<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Calvin McLean</title>
    <description>The latest articles on Forem by Calvin McLean (@calvinmclean).</description>
    <link>https://forem.com/calvinmclean</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1117309%2Fd2f5c9c4-7f41-48ec-88b3-fc33a8b275b7.jpeg</url>
      <title>Forem: Calvin McLean</title>
      <link>https://forem.com/calvinmclean</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/calvinmclean"/>
    <language>en</language>
    <item>
      <title>Experiment with gopls MCP: Improving Agent Context for Go Development</title>
      <dc:creator>Calvin McLean</dc:creator>
      <pubDate>Tue, 09 Sep 2025 03:24:42 +0000</pubDate>
      <link>https://forem.com/calvinmclean/experiment-with-gopls-mcp-improving-agent-context-for-go-development-37bo</link>
      <guid>https://forem.com/calvinmclean/experiment-with-gopls-mcp-improving-agent-context-for-go-development-37bo</guid>
      <description>&lt;p&gt;When working with LLMs, managing the context size is really important. &lt;a href="https://dev.to/calvinmclean/how-to-implement-llm-tool-calling-with-go-and-ollama-237g"&gt;I previously learned&lt;/a&gt; that a chat session with an LLM or agent is stored as a continuous array of messages and is re-processed by the model for each prompt. As the conversation or coding sessions goes on, the amount of data the model has to process grows. This eventually erodes the responses since they are processing too much information. It is best to keep sessions short, concise, and on-topic.&lt;/p&gt;

&lt;p&gt;When you are using an agent to write code, it constantly has to search directories and read files. A lot of this information is not relevant to the goal and contributes to the context rot. This is something that the experimental Model Context Protocol (MCP) server in &lt;code&gt;gopls&lt;/code&gt; aims to fix. This tool provides direct access to some parts of the language server protocol (LSP) for Go. Some of these tools, like &lt;code&gt;go_package_api&lt;/code&gt;, which fetches the exported functions, types, and comments for a Go package, can be really useful to learn how to use a package. Additionally, &lt;code&gt;go_search&lt;/code&gt; provides a way to search a codebase and its dependencies without reading all of the implementation code. &lt;code&gt;go_symbol_references&lt;/code&gt; allows the agent to search where a function is used so it can understand the impact of a change without searching a large number of files.&lt;/p&gt;

&lt;p&gt;I want to take a deeper dive into &lt;code&gt;gopls&lt;/code&gt; MCP and how (or if) it can improve coding agents. Specifically, I want to see how it can improve an agent's ability to use external packages where the source code is not readily available. One way to get around this is to use &lt;code&gt;go mod vendor&lt;/code&gt; to get a local copy of dependencies. However, this is not efficient because the agent really only cares about how to use a package. Reading in all of the implementation source code is a waste of the precious context. An abundance of source code in the context can also cause an agent to start copying pieces of code and style choices from dependencies which is not ideal. If you are using a popular package with tons of open source examples in the LLMs training set, you might not notice this issue. However, it is a huge drawback when trying to use new versions of packages, private internal libraries, or more obscure packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Experiment
&lt;/h2&gt;

&lt;p&gt;In order to learn more about the &lt;code&gt;gopls&lt;/code&gt; MCP and see how it improves an agent, I will do an experiment. This is not the most scientific experiment and is largely based on user experience and vibes. The experiment will work by attempting to create a new program using a lesser-known library. This library is &lt;code&gt;babyapi&lt;/code&gt;, which I wrote to simplify REST API development. This package is pretty unknown and has an eclectic feature set based around things I found interesting or useful.&lt;/p&gt;

&lt;p&gt;I will start with a simple project with the module initialized and &lt;code&gt;github.com/calvinmclean/babyapi&lt;/code&gt; imported.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;go.mod&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module customers

go 1.24

require (
    github.com/calvinmclean/babyapi v0.31.0
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;main.go&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="s"&gt;"github.com/calvinmclean/babyapi"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is my initial prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use github.com/calvinmclean/babyapi to create a REST API for customer relationship management. This should support babyapi's feature for end-dating resources. It should also use babyapi's CLI and support saving to JSON file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This prompt doesn't provide any specific function names and generally requests features that I know are provided by &lt;code&gt;babyapi&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I am using Github Copilot's free tier to access the GPT-4.1 model.&lt;/p&gt;

&lt;p&gt;First, I will run the prompt without the &lt;code&gt;gopls&lt;/code&gt; MCP server. Then, I will start fresh, &lt;a href="https://dev.to/calvinmclean/how-to-use-gopls-mcp-with-vs-code-11ha"&gt;setup the MCP server and system instruction&lt;/a&gt;, and run the prompt again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 1: VS Code without gopls MCP
&lt;/h2&gt;

&lt;p&gt;I was surprised to see that the first thing the agent did is fetch the documentation from &lt;code&gt;pkg.go.dev/github.com/calvinmclean/babyapi&lt;/code&gt;. It then implemented this perfectly on its first attempt.&lt;/p&gt;

&lt;p&gt;The end-dating and JSON storage features are directly mentioned and used in examples in my project's README file, so this was easy to implement. Interestingly, the agent also enabled babyapi's built-in MCP server option on the customers API. This feature is also mentioned in the README and probably had a high relevance due to the agent's interest in MCP.&lt;/p&gt;

&lt;p&gt;This is the first time I saw an agent fetch documentation like this, so I was impressed to see how well it worked. Unfortunately, it doesn't leave much room for improvement provided by the MCP. The project documentation is somewhat large, so maybe the MCP will be able to reduce the context size by only loading relevant information. Fetching the documentation might not be as effective in larger packages or sparsely-documented ones, so the &lt;code&gt;gopls&lt;/code&gt; MCP is still worth exploring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 2: VS Code with gopls MCP
&lt;/h2&gt;

&lt;p&gt;Next, I start fresh in a new project, setup the MCP server, the default system instruction, and provide the same prompt. The first attempt here did not work well at all.&lt;/p&gt;

&lt;p&gt;It started by using &lt;code&gt;go_package_api&lt;/code&gt; to describe the &lt;code&gt;babyapi&lt;/code&gt; package and then implemented the base functionality. Then, it spent a lot of time using the &lt;code&gt;go_search&lt;/code&gt; tool to figure out how to end-date and save to file storage. It used a variety of search queries like "file storage", "storage", "NewFileDB",  and "NewKVStorage". Each got closer and closer to the correct function which is &lt;code&gt;kv.NewFileDB&lt;/code&gt;. Then, it started searching for a way to end-date the resources. Eventually, it timed out with a nearly-correct implementation. The only problem was using the incorrect type for the &lt;code&gt;NewFileDB&lt;/code&gt; config input. Althought I cannot see the context size, it seems like this likely used more tokens due to the long iteration.&lt;/p&gt;

&lt;p&gt;The agent with the MCP was at a disadvantage since the KV Storage example is explicitly spelled-out in the package README, but the &lt;code&gt;go_package_api&lt;/code&gt; only gets documentation from the Go file comments. However, it was interesting to see how the agent was able to iterate using the search tools.&lt;/p&gt;

&lt;p&gt;I was curious about how the context usage compares between these two experiments, but VS Code does not show the token usage. For the next attempts, I will switch over to my favorite editor: Zed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 3: Zed without gopls MCP
&lt;/h2&gt;

&lt;p&gt;Zed is able to use the same model provided by Github, so the only difference here will be the agent implementation by Zed. This time, the agent did not start by reading the documentation and came up with a totally halucinated implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;babyapi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Register the Customer resource with end-dating enabled&lt;/span&gt;
    &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RegisterResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;babyapi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResourceConfig&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;]{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;"customer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Store&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;babyapi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewJSONStore&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="s"&gt;"customers.json"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;EndDating&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// Enables end-dating (soft delete)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c"&gt;// Start the API server (default on :8080)&lt;/span&gt;
    &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the documentation is not fetched, it's clearly worse than the agent using &lt;code&gt;gopls&lt;/code&gt; MCP. Since the agents operated by VS Code and Zed have different system prompts and tooling around them, they tackle this problem differently. The &lt;code&gt;gopls&lt;/code&gt; MCP will enable general functionality that is independent of the agent being used. This means a lesser, and probably cheaper agent can reach similar functionality to more advanced ones.&lt;/p&gt;

&lt;p&gt;In order to estimate the size of the context that comes from reading the documentation, the prompt is modified to improve the agent's behavior:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use github.com/calvinmclean/babyapi to create a REST API for customer relationship management. This should support babyapi's feature for end-dating resources. It should also use babyapi's CLI and support saving to JSON file. Start by reading the documentation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With the imprved prompt, it implemented the program nearly identically to the first attempt with VS Code. This used approximately 26k tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 4: Zed with gopls MCP
&lt;/h2&gt;

&lt;p&gt;After struggling a bit to get the &lt;code&gt;gopls&lt;/code&gt; MCP working with Zed, I continued the next step of the experiment. This had a similar problem compared to VS Code and did a lot of iteration using &lt;code&gt;go_search&lt;/code&gt;. It eventually used 35k tokens to get a mostly correct solution. It is unfortunate to see that the gopls MCP is not reducing the context size so far.&lt;/p&gt;

&lt;p&gt;I spent some more time iterating on the prompt and analyzing the agent logs. Eventually, I came up with this addition to the base prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Start by using Gopls mcp to describe the babyapi package. Instead of searching files by regex, use gopls MCP's go_search.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Before this change, the agent wasn't always using the &lt;code&gt;go_package_api&lt;/code&gt; tool, so I instruced it to do so. It was using regex searches on the codebase when trying to find &lt;code&gt;babyapi&lt;/code&gt; functions which was not working. The &lt;code&gt;go_search&lt;/code&gt; tool will also search dependencies, so it should use that instead. With the new prompt, it provided a working solution with only 23k tokens. Interestingly, instead of using babyapi's built-in KVStorage, the agent fully implemented the &lt;code&gt;babyapi.Stoage&lt;/code&gt; interface with its own file storage mechanism. Had it used the built-in solution, it might have used even fewer tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;While the gopls MCP sounds really promising for improving an agent's ability to write Go code, it isn't an absolute game changer right now. While working on these experiments, I learned more about general strategies for using agents rather than learning directly about the impacts of the gopls MCP. Some of these lessons are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The prompt is really important. Even with a repository instruction/rule describing how to use the &lt;code&gt;gopls&lt;/code&gt; MCP, it was useful to explicitly tell the agent to prioritize it over built-in tools&lt;/li&gt;
&lt;li&gt;When you know a specific function that you want to use, it's best to spell it out directly instead of describing it generally. With the addition of the &lt;code&gt;gopls&lt;/code&gt; MCP, this strategy can become even more effective since the agent can effeciently search for this symbol to learn how to use it&lt;/li&gt;
&lt;li&gt;Agents can consume a lot of context. Instructing it to simply read documentation can be really effective if the documentation is well-written. This of course depends on your goal. If you are looking to use one specific function, it might be overkill to consume the whole documentation&lt;/li&gt;
&lt;li&gt;Agents are best at iterating. Rather than attempting to get a perfect one-shot prompt, pay attention to the agent's output. Interrupt it and correct it early in the process if you see something incorrect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In regards to the &lt;code&gt;gopls&lt;/code&gt; MCP, my example usecase is probably not the most useful one. As demonstrated, simply reading the documentation is sufficient for smaller, well-documented libraries. LLMs are trained on existing code and like to copy, so reading examples is more effective than analyzing function signatures.&lt;/p&gt;

&lt;p&gt;I expect that the &lt;code&gt;gopls&lt;/code&gt; MCP will work better to augment development in large codebases where the agent can benefit from the &lt;code&gt;go_search&lt;/code&gt; and &lt;code&gt;go_symbol_references&lt;/code&gt; tools. Also, in a more extended and iterative development example, loading up the whole documentation might pollute the context too much, especially when using multiple packages. I plan to continue using it in my real development flow to observe how it works in a larger real-world usecase. While it didn't crush this example, it is still promising.&lt;/p&gt;

&lt;p&gt;Another interesting strategy for working with external libraries is to use a multi-agent workflow. A second agent can be used to browse a variety of relevant documentation. Then, it will extract or summarize the most important pieces and provide that smaller set of data to the main agent.&lt;/p&gt;

&lt;p&gt;Ultimately, I learned that good prompting is still the best way to get good results from an agent. The &lt;code&gt;gopls&lt;/code&gt; MCP is another tool that, when used properly, can improve the workflow, but is not a perfect solution. It can be used along with good prompts to guide an agent to the best solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Before I heard about the &lt;code&gt;gopls&lt;/code&gt; MCP, I started theorizing about and working on an idea to improve an agent's ability to get relevant context about a library. This idea is to use retrieval augmented generation (RAG) by vectorizing and semantic-searching documentation. This will provide documentation that is directly relevant to what the agent is looking for. My initial idea was to use information from &lt;code&gt;pkg.go.dev&lt;/code&gt;, but I ended up parsing Go files directly to read documentation comments. This was easier to parse and link directly to the code.&lt;/p&gt;

&lt;p&gt;After hearing about the gopls MCP, I felt discouraged about this idea and thought that it might no longer be relevant. This experiment proved otherwise. The agent that read documentation was really effective. While reading the whole documentation worked well, using the vectorized data could reduce overall context since the agent won't need to load the whole documentation into each session. Additionally, it should pair really well with the &lt;code&gt;gopls&lt;/code&gt; MCP. The semantic search will find relevant symbol and file names, and &lt;code&gt;gopls&lt;/code&gt; can be used to look them up and get more details if needed. I will continue working on this and rerun the experiments to see if there are any benefits. I learned here that I should also parse a library's README since it can be more useful than the code comments. Keep an eye out for my next post where I will share these results!&lt;/p&gt;

&lt;p&gt;Thank you for reading! If you learned anything new or have any advice, please let me know in the comments.&lt;/p&gt;

</description>
      <category>go</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>How to use gopls MCP with VS Code</title>
      <dc:creator>Calvin McLean</dc:creator>
      <pubDate>Tue, 02 Sep 2025 04:14:43 +0000</pubDate>
      <link>https://forem.com/calvinmclean/how-to-use-gopls-mcp-with-vs-code-11ha</link>
      <guid>https://forem.com/calvinmclean/how-to-use-gopls-mcp-with-vs-code-11ha</guid>
      <description>&lt;p&gt;The Go team recently added an official &lt;a href="https://tip.golang.org/gopls/features/mcp" rel="noopener noreferrer"&gt;Model Context Protocol (MCP) server to &lt;code&gt;gopls&lt;/code&gt;&lt;/a&gt;, the language server protocol (LSP) implementation for Go. This allows IDEs and other AI coding agents to directly access some features of the LSP. While most of the features provided by the MCP already exist in AI coding tools, this can improve the efficiency of token use by avoiding searching and reading whole files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enable gopls MCP in VS Code
&lt;/h2&gt;

&lt;p&gt;Now, let's get to the reason you're here! With a few simple steps, we can start using this new MCP feature in VS Code:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Install/update &lt;code&gt;gopls&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;golang.org/x/tools/gopls@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;I currently have Go &lt;code&gt;1.24.0&lt;/code&gt; with &lt;code&gt;v0.20.0&lt;/code&gt; of &lt;code&gt;gopls&lt;/code&gt;, but newer versions exist at the time of writing this&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Enable VS Code to run the server
&lt;/h3&gt;

&lt;p&gt;VS Code automatically runs the &lt;code&gt;gopls&lt;/code&gt; server for Go projects. A simple update to the config will also start the MCP server.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to Settings and find &lt;code&gt;Go: Language Server Flags&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Edit in settings.json&lt;/code&gt; and add:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"go.languageServerFlags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"-mcp.listen=localhost:8092"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Restart/reload VS Code&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Enable the MCP server for agents
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use the &lt;code&gt;MCP: Add Server...&lt;/code&gt; command&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;HTTP&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Input the address: &lt;code&gt;http://localhost:8092&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Alternatively, just edit &lt;code&gt;.vscode/settings.json&lt;/code&gt; with this content:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"gopls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8092"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Setup instructions for using the tools
&lt;/h3&gt;

&lt;p&gt;The Go team provides a base instruction to improve how agents use the feature. Setting up VS Code to use this feature will greatly improve how it uses the MCP tools.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, create the instructions directory
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .github/instructions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Then, start by telling VS Code to use this for Go files:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt; &amp;gt; .github/instructions/gopls.instructions.md
  ---
  applyTo: "**/*.go"
  ---
&lt;/span&gt;&lt;span class="no"&gt;  EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Finally, add the &lt;code&gt;gopls&lt;/code&gt; instruction:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  gopls mcp &lt;span class="nt"&gt;-instructions&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .github/instructions/gopls.instructions.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Use it!
&lt;/h2&gt;

&lt;p&gt;Now you should have &lt;code&gt;gopls&lt;/code&gt; MCP setup in your Go project! Open up the agent chat and start coding!&lt;/p&gt;

&lt;p&gt;The simplest place to start is to ask about references for a function or type in your code: "Describe where MyFunction is used."&lt;/p&gt;

&lt;p&gt;This should use the &lt;code&gt;go_symbol_references&lt;/code&gt; tool to find references to this function.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why MCP?
&lt;/h3&gt;

&lt;p&gt;One of the most impactful features is the ability to use the &lt;code&gt;go_diagnostics&lt;/code&gt; tool. Besides the usual information about diagnostic issues, this also allows the agent to automatically review and apply fixes suggested by the LSP. This greatly improves consistency and accuracy of code fixes since it uses deterministic analysis provided directly by the Go maintainers.&lt;/p&gt;

&lt;p&gt;If you are regularly programming with newer versions of Go, you might be familiar with the "modernize" suggestions. I most-commonly see these with typical &lt;code&gt;for i := 0; i &amp;lt; 10; i++&lt;/code&gt; loops that could use the new &lt;code&gt;for i := range 10&lt;/code&gt; syntax. This feature is a big win for the language in the world of AI coding. By definition, LLMs always output "old" code since they are trained on existing codebases. This means any new language features won't be used by LLMs until enough human coders have introduced them into the training data.&lt;/p&gt;

&lt;p&gt;The new &lt;a href="https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/modernize" rel="noopener noreferrer"&gt;analyze and modernize&lt;/a&gt; tools allow coding agents to automatically update to new language features that they don't even know about yet. This improves the overall quality of a codebase even when using AI agents. This is an important feature helping to keep Go relevant as software development is rapidly changing.&lt;/p&gt;

&lt;p&gt;To demonstrate this tool, write a simple function using a for loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Example&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, World!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, ask the agent to run diagnostics on it! It should look something like this:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcalvinmclean%2Fcalvinmclean.github.io%2Fmain%2Farticles%2Fgopls-mcp-vscode%2Fgo_diagnostics_example.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcalvinmclean%2Fcalvinmclean.github.io%2Fmain%2Farticles%2Fgopls-mcp-vscode%2Fgo_diagnostics_example.png" title="Go Diagnostic Agent Example" alt="Go Diagnostic Agent Example" width="800" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>How to Implement LLM Tool-calling with Go and Ollama</title>
      <dc:creator>Calvin McLean</dc:creator>
      <pubDate>Mon, 16 Jun 2025 00:43:11 +0000</pubDate>
      <link>https://forem.com/calvinmclean/how-to-implement-llm-tool-calling-with-go-and-ollama-237g</link>
      <guid>https://forem.com/calvinmclean/how-to-implement-llm-tool-calling-with-go-and-ollama-237g</guid>
      <description>&lt;p&gt;Tool-calling is a feature of LLMs and LLM APIs that enables the model to access external information and perform actions. Instead of responding to a prompt directly, the model generates inputs to a function, calls that function (more on this later), and then generates a response based on the function's output. This significantly improves the functionality of LLMs by accessing real-time information like weather, sports scores, or recent events. Additionally, the model can interact with external systems like databases, APIs, or even execute scripts.&lt;/p&gt;

&lt;p&gt;LLMs and tool-calling introduced a new type of program where the operation and flow is controlled by the LLM rather than rigid code. This creates highly flexible and dynamic programs, but they can also be less reliable and consistent. Like always, it's important to balance functionality and reliability.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;LLMs are only able to generate text. In order to use tools, the model needs to be prompted &lt;em&gt;through&lt;/em&gt; another program or controller. Instead of actually "calling" a function directly, the the model generates structured data to describe the function to use and which inputs to use with it. The controller program is responsible for parsing the input data, finding the function, and executing it. Then, it provides the output to the LLM (often as JSON). The LLM is finally able to respond to the user's prompt using the output of the function in addition to the data it was trained on.&lt;/p&gt;

&lt;p&gt;Before using tools, the LLM needs to know about the tools that it can use. It needs the function's name, a description of what it does, and a schema defining the function's inputs. This can be done with system prompts, but many model APIs now include a separate input field for tool definitions.&lt;/p&gt;

&lt;p&gt;Interacting with the model directly, with only a thin chat abstraction around it, effectively demonstrates how this works. Ollama allows creating a Modelfile to extend a model with a specific system prompt or other configurations. Here is a system prompt that instructs a model how to use "tools" (but really it will just be me responding with JSON):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM gemma3:4b

SYSTEM """
A program is used to interact with you and the user. The program allows you to use functions/tools
to get external information. When you use a tool, the program will respond with JSON data. Anything else is
coming from the user and should be treated as human interaction.

To use a tool, respond with a JSON object with the following structure:
{
    "tool": &amp;lt;name of the called tool&amp;gt;,
    "tool_input": &amp;lt;parameters for the tool matching the above JSON schema&amp;gt;
}
Do not include any other text in your response.

If the user's prompt requires a tool to get a valid answer, use the above format to use the tool.
After receiving a tool response, continue to answer the user's prompt using the tool's response.
If you don't have a relevant tool for the prompt, answer it normally. Be fun and interesting.

Here are your available tools:

getCurrentWeather
Get the current weather in a given location
{
    "type": "object",
    "properties": {
        "location": {"type": "string", "description": "The city and state, e.g. San Francisco, CA"},
        "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
    },
    "required": ["location", "unit"]
}
"""
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that the model is ready, let's chat:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Input:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;what is the weather like in new york city?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Model's response:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"getCurrentWeather"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tool_input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"New York City"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fahrenheit"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Input:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"temperature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;78&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"F"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Model's response:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The temperature in New York City is currently 78°F! It’s a beautiful, warm day – perfect for a stroll through Central Park or grabbing a hot dog.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Instead of attempting to hallucinate a response, the model generates tool input using the specified format. Then, I respond with JSON emulating the tool output. With this new information, the model is able to accurately answer the prompt with a friendly flair.&lt;/p&gt;

&lt;p&gt;Although this isn't real tool calling, it is interesting to see how impactful the system prompt is on the model's behavior. This is helpful to demystify the LLM's behavior and show what is really happening behind the scenes. This foundational understanding beneficial when developing more complex tools and building applications around LLMs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implement Tool-calling with Ollama + Go
&lt;/h2&gt;

&lt;p&gt;The next step is to integrate tool calling into a real program instead of chatting with the model. In order to stay at a low-level and learn more about LLM interactions, the program uses Ollama's API and Go client instead of a more generalized library.&lt;/p&gt;

&lt;p&gt;Here is the request used to prompt a model with a tool defined:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"What is the weather like in New York City?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Messages&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Tools&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tools&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToolFunction&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="s"&gt;"getCurrentWeather"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Get the current weather in a given location"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToolFunctionParameters&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;Required&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="n"&gt;Properties&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToolFunctionProperty&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="s"&gt;"location"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PropertyType&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                                &lt;span class="n"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"The city and state, e.g. San Francisco, CA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client program sends the ChatRequest and handles the ToolCall in the model's response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/json"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/ollama/ollama/api"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"llama3.2:3b"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientFromEnvironment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"What is the weather like in New York City?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// ... request from above&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// If there is no tool call, just print the message content&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToolCalls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// Otherwise, process the tool call&lt;/span&gt;
        &lt;span class="n"&gt;tc&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToolCalls&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;
        &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"getCurrentWeather"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;getCurrentWeather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Arguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"tool"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invalid function: %q"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Send initial chat message&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// The model should have responded with a tool call&lt;/span&gt;
    &lt;span class="c"&gt;// and the handler would have appended a response to messages.&lt;/span&gt;
    &lt;span class="c"&gt;// Now, call the tool again with the response&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getCurrentWeather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"bad args: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;weatherInfo&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"location"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"temperature"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"80"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"unit"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="s"&gt;"fahrenheit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"forecast"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"sunny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"windy"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weatherInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output should be something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The current weather in New York City is mostly sunny, with a windy condition. The temperature is at 80°F.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Going a step further
&lt;/h2&gt;

&lt;p&gt;I happen to have a program that is perfect for demonstrating simple LLM integration using tools. The &lt;a href="https://github.com/calvinmclean/walks-of-italy" rel="noopener noreferrer"&gt;&lt;code&gt;walks-of-italy&lt;/code&gt;&lt;/a&gt; was originally created to watch for new tour availability from &lt;a href="https://www.walksofitaly.com" rel="noopener noreferrer"&gt;Walks of Italy&lt;/a&gt; tour company. I started with a grander vision for this program: a tool that could watch, search, and observe tour availability. After implementing the base feature of notifying on new availability, I decided it was good enough for my use-case.&lt;/p&gt;

&lt;p&gt;Now, with LLM tools, this program can be extended in any number of ways. Here are the tools it currently has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;getAllTours&lt;/code&gt;: list the tour names and their UUIDs from the database&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getTourDetails&lt;/code&gt;: use the API to access the tour descriptions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getTourAvailability&lt;/code&gt;: use the API to check availability, prices, and capacity at certain times&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With just a few tools, the LLM has these features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simply query a date range with natural language

&lt;ul&gt;
&lt;li&gt;"what is the availability of the Pristine Sistine tour next week?"&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Ask a more general question about multiple tours

&lt;ul&gt;
&lt;li&gt;"what is the availability of vatican tours on July 5th?"&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Get travel recommendations

&lt;ul&gt;
&lt;li&gt;"I am going to be in Rome on August 3rd and 4th. Which tours can I go on?"&lt;/li&gt;
&lt;li&gt;"If I can only do one tour in Rome, which should I do?"&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;When asked "what is the availability of the Pristine Sistine tour next week?" the model responds:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Here's an overview of the availability for the tours:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;June 20, 2025&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There are limited spots available for the 7:15 AM and 7:45 AM tours.&lt;/li&gt;
&lt;li&gt;The 7:15 AM tour has 1 vacancy out of a capacity of 20.&lt;/li&gt;
&lt;li&gt;The 7:45 AM tour also has 1 vacancy out of a capacity of 48.&lt;/li&gt;
&lt;li&gt;The 6:45 AM tour is fully booked.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;June 21, 2025&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The 6:45 AM tour has 10 available spots out of 10 total.&lt;/li&gt;
&lt;li&gt;The 7:15 AM tour has 3 vacancies out of a capacity of 34.&lt;/li&gt;
&lt;li&gt;The 7:45 AM tour has 1 vacancy out of a capacity of 28.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Other dates&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On June 19, 2025, the 7:45 AM tour is fully booked.&lt;/li&gt;
&lt;li&gt;On June 18, 2025, the 7:15 AM tour is fully booked, and the 7:45 AM tour has limited availability.&lt;/li&gt;
&lt;li&gt;On June 17, 2025, the 7:15 AM tour is fully booked, and the 7:45 AM tour has limited availability.&lt;/li&gt;
&lt;li&gt;On June 16, 2025, the 7:45 AM tour is fully booked.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're looking for availability, the best options are on June 20 and June 21, with limited spots available for the morning tours. Let me know if you'd like help booking!&lt;/p&gt;

&lt;p&gt;These are just a few examples that I can think of. A huge benefit to this type of program is that its functionality isn't only limited by the features added by a developer. Instead, it's limited by the LLM and the tools that it can use. The user might come up with a usecase that works out of the box due to the program's flexibility. In this example, I justed wanted to check specific tours and dates for availability, but can now do a lot more.&lt;/p&gt;

&lt;p&gt;The effectiveness and accuracy of the responses here of course varies and is dependent on the model that is used. More complex questions that require more tool calls will generally decrease the accuracy of responses. When a lot of information is required, larger and more capable models are required.&lt;/p&gt;

&lt;p&gt;In this example, the functionality could be made even more flexible. Currently, it just is able to use tours that I manually add. If it has a new function to get tours from the Walks of Italy website, it could be even more dynamic (but less predictable).&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP
&lt;/h2&gt;

&lt;p&gt;Model Context Protocol, or MCP, is the next evolution of tool calling. Instead of requiring a custom program to provide tools, it moves this to the server-side. The model is used with a generic client that supports the protocol and accesses functions provided by any number of external servers. If this previous example used MCP, Walks of Italy would provide an MCP server any model would be able to access it when used with an MCP client. It would eliminate almost all of the work done to implement &lt;a href="https://github.com/calvinmclean/walks-of-italy" rel="noopener noreferrer"&gt;my &lt;code&gt;walks-of-italy&lt;/code&gt; program&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thoughts
&lt;/h2&gt;

&lt;p&gt;Until recently, I primarily interacted with LLMs using ChatGPT or Gemini to answer questions, discuss ideas, and sometimes generate SQL queries or code. As AI Agents become more popular and integrated into all parts of our lives and jobs, it starts to seem like LLMs can do anything. It was refreshing to start from the barebones model interactions and really see how tool-calling works. In reality, LLMs are still just reading input and generating output. It's the code around them, built by software engineers, that enables them to actually &lt;em&gt;do&lt;/em&gt; things.&lt;/p&gt;

&lt;p&gt;Building an application around an LLM and tools can be really beneficial. If the model has access to a set of tools with general functionality like querying APIs and DBs (with pre-defined SQL of course), the program's functionality can change and expand on-demand. This is especially useful for non-technical users. Instead of asking engineers to build new features for a specific usecase, they can just adjust their prompts and interactions with the LLM. In the previous example, the &lt;code&gt;walks-of-italy&lt;/code&gt; program was originally designed to watch and alert for new tour availability or run specific date range queries with the CLI. The introduction of a few tools and an LLM drastically expanded the scope of the program and makes it more useful for non-technical users. Now that the LLM can query any date range and get descriptions about tours, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get help deciding which tours to go based on real pricing, descriptions, and availability&lt;/li&gt;
&lt;li&gt;Ask about general availability next month for multiple tours&lt;/li&gt;
&lt;li&gt;Compare availability and prices for weekdays or weekends&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is important to remember that the model can behave unpredictably and the tools should not provide unlimited access. For example, instead of providing a tool to execute any generated SQL, engineers should expose a few functions like "createResource" and "updateResource" with pre-defined queries.&lt;/p&gt;

&lt;p&gt;System prompts and tool definitions provide more structure and control over how an LLM operates. An engineer can build a relatively reliable and useful program with these techniques. However, as complexity of the system grows, the potential for the LLM to do unpredictable things also grows.&lt;/p&gt;

&lt;p&gt;Normally, code is very predictable. A set of functions exists and engineers define exactly when and how they are used. Now, programs become a collection of tools and the actual control is being relinquished to LLMs. Engineers always have to balance complexity, maintainability, and functionality. Using LLMs just makes this balance more delicate.&lt;/p&gt;

</description>
      <category>go</category>
      <category>tutorial</category>
      <category>learning</category>
      <category>programming</category>
    </item>
    <item>
      <title>Middleware and RoundTrippers in Go</title>
      <dc:creator>Calvin McLean</dc:creator>
      <pubDate>Sun, 01 Jun 2025 03:46:10 +0000</pubDate>
      <link>https://forem.com/calvinmclean/middleware-and-roundtrippers-in-go-30pa</link>
      <guid>https://forem.com/calvinmclean/middleware-and-roundtrippers-in-go-30pa</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/calvinmclean/calvinmclean.github.io/blob/main/examples/middleware-roundtripper" rel="noopener noreferrer"&gt;Check out the full code referenced in this article on Github!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Middleware and RoundTrippers are common web-development tools used in Go programs. Middleware operate on the server-side, while RoundTrippers are client-side. They are both reusable components that execute code before and/or after a request and response are processed.&lt;/p&gt;

&lt;p&gt;In this article, I will provide some details about Middleware and RoundTrippers before providing interesting examples of each. Keep in mind that these are intended to be easily-understood examples and are not meant to be used in real applications as-is.&lt;/p&gt;

&lt;p&gt;Then, tests will be used to demonstrate the correct and incorrect usage of these tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Middleware
&lt;/h2&gt;

&lt;p&gt;A middleware is just an HTTP handler (defined as &lt;code&gt;ServeHTTP(ResponseWriter, *Request)&lt;/code&gt; &lt;a href="https://pkg.go.dev/net/http#Handler" rel="noopener noreferrer"&gt;in Go's standard library&lt;/a&gt;) that calls another HTTP handler before and/or after doing some other actions. This is commonly used for logging and authentication. In the case of auth, it allows you to wrap every handler with the same auth middleware. If the middleware is applied to every handler in the application, the handlers can all operate assuming the request is already authenticated. A logging middleware allows you to observe every request's details as well as the response's status and duration. Go's standard library even provides a few middleware already: &lt;a href="https://pkg.go.dev/net/http#StripPrefix" rel="noopener noreferrer"&gt;StripPrefix&lt;/a&gt; and &lt;a href="https://pkg.go.dev/net/http#TimeoutHandler" rel="noopener noreferrer"&gt;TimeoutHandler&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache Middleware
&lt;/h3&gt;

&lt;p&gt;This middleware caches responses on the server-side to prevent duplicate work. Implementing this simple middleware can result in a significant performance boost in applications with frequent &lt;code&gt;GET&lt;/code&gt; requests. It can even reduce operational costs in cloud environments that charge for compute time, bandwidth, or traffic to databases. This can also be combined with an external cache, like Redis, to be functional across multiple instances.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;CacheMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;cacheKey&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%s_%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

        &lt;span class="c"&gt;// use cached response of the key matches&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;serverLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SERVER using cached response"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Server-Cached"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Buffer&lt;/span&gt;
        &lt;span class="c"&gt;// responseWriter is an implementation of the http.ResponseWriter&lt;/span&gt;
        &lt;span class="c"&gt;// interface that can intercept the response body&lt;/span&gt;
        &lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;responseWriter&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// Call the main HTTP handler&lt;/span&gt;
        &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c"&gt;// Cache the response&lt;/span&gt;
        &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Logger Middleware
&lt;/h3&gt;

&lt;p&gt;This example simply logs request and response details.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;LoggerMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;serverLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SERVER Request: %s %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Buffer&lt;/span&gt;
        &lt;span class="c"&gt;// responseWriter is an implementation of the http.ResponseWriter&lt;/span&gt;
        &lt;span class="c"&gt;// interface that can intercept the response body&lt;/span&gt;
        &lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;responseWriter&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// Call the main HTTP handler&lt;/span&gt;
        &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;serverLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SERVER Response: %s %s %d %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Since&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Auth Middleware
&lt;/h3&gt;

&lt;p&gt;This is an simplified example of a middleware that reads the &lt;code&gt;Authorization&lt;/code&gt; header and authenticates requests. It also removes the header so the secret/password is not at risk of logging or caching.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;AuthMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;serverLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SERVER Authentication failed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusForbidden&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Forbidden"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// remove header to avoid logging&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Del&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;serverLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SERVER Authentication successful"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c"&gt;// Call the main HTTP handler&lt;/span&gt;
        &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Recovery Middleware
&lt;/h3&gt;

&lt;p&gt;This middleware is essential in any webserver applications. Go runs a goroutine for each incoming request, but a &lt;code&gt;panic&lt;/code&gt; will cause the whole application to crash. This middleware uses &lt;code&gt;recover()&lt;/code&gt; to "catch" a panic, log it, and avoid crashing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;RecoveryMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;recover&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;serverLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SERVER panic: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}()&lt;/span&gt;

        &lt;span class="c"&gt;// Call the main HTTP handler&lt;/span&gt;
        &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sequential Middleware
&lt;/h3&gt;

&lt;p&gt;This final example is a middleware that simplifies the composition of multiple middleware. Instead of using nested function calls, all of the middleware can be provided as a variadic argument.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;SequentialMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;middleware&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mw&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;slices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Backward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;final&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;final&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is what it looks like to use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;SequentialMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;RecoveryMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;AuthMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;LoggerMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CacheMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;helloHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// easier to read than:&lt;/span&gt;
&lt;span class="n"&gt;RecoveryMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AuthMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LoggerMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CacheMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;helloHandler&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition to being easier to read and format, this can be used to create a slice of middleware (&lt;code&gt;[]func(http.HandlerFunc) http.HandlerFunc&lt;/code&gt;) based on configuration values.&lt;/p&gt;

&lt;h2&gt;
  
  
  RoundTrippers
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://pkg.go.dev/net/http#RoundTripper" rel="noopener noreferrer"&gt;RoundTrippers&lt;/a&gt; seem to be less common than middleware, but are very useful for client-side HTTP requests. It is called a RoundTripper because it handles the round-trip from the client to the server. This is shown by the interface's single method signature: &lt;code&gt;RoundTrip(*Request) (*Response, error)&lt;/code&gt;. It takes a request and provides the response.&lt;/p&gt;

&lt;p&gt;RoundTrippers are used by setting the &lt;code&gt;http.Client&lt;/code&gt;'s &lt;code&gt;Transport&lt;/code&gt; field. Unless you want to re-implement the code for establishing network connections between the client and the server, your RoundTripper implementations must, at some point, wrap Go's &lt;a href="https://pkg.go.dev/net/http#DefaultTransport" rel="noopener noreferrer"&gt;DefaultTransport&lt;/a&gt;. This, in my opinion, is a key distinction that makes RoundTrippers a bit more difficult to use than middleware. A middleware always uses your own code, but a custom RoundTripper needs to have the DefaultTransport somewhere in the chain. Therefore, almost any custom RoundTripper must wrap another RoundTripper.&lt;/p&gt;

&lt;h3&gt;
  
  
  RoundTripperFunc
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;RoundTripperFunc&lt;/code&gt; is similar to Go's &lt;code&gt;http.HandlerFunc&lt;/code&gt; and simplfies the creation of RoundTrippers. Instead of creating a struct and implementing &lt;code&gt;RoundTrip&lt;/code&gt;, this type allows implementing just the function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;RoundTripperFunc&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt; &lt;span class="n"&gt;RoundTripperFunc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;RoundTrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rt&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultTransport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cache RoundTripper
&lt;/h3&gt;

&lt;p&gt;This RoundTripper reduces network traffic and latency by using cached responses instead of making another round-trip to the server. Similar to the &lt;code&gt;CacheMiddleware&lt;/code&gt;, this can provide a significant performance boost to applications with frequent external requests. When interfacing with vendor APIs that charge per-request, this can also signficantly reduce operational costs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;CacheRoundTripper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTripper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTripper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;cachedResponse&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;RoundTripperFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;cacheKey&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%s_%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

        &lt;span class="n"&gt;cachedResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;clientLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CLIENT using cached response"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cachedResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// send the actual request&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c"&gt;// cache the response&lt;/span&gt;
        &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newCachedResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;cachedResponse&lt;/code&gt; type is excluded here to keep the example short. This is just a new struct that copies the &lt;code&gt;http.Response&lt;/code&gt; and its body. You can check out the full example code &lt;a href="https://github.com/calvinmclean/calvinmclean.github.io/blob/main/examples/middleware-roundtripper" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Log RoundTripper
&lt;/h3&gt;

&lt;p&gt;This is very similar to the log middleware and just logs the request and response details.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;LogRoundTripper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTripper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTripper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;RoundTripperFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;clientLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CLIENT Request: %s %s %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c"&gt;// Send the actual request&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Since&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;clientLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CLIENT Request failed after %s: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;clientLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CLIENT Response: %s %s %d in %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Auth RoundTripper
&lt;/h3&gt;

&lt;p&gt;This is a simplified example of using a RoundTripper for adding auth to a request. In this case, it just adds a password to the &lt;code&gt;Authorization&lt;/code&gt; header. Even though this is a simple case, it is still useful because only one part of your application needs to be aware of the password/secret and the rest will reuse the RoundTripper.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;AuthRoundTripper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTripper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTripper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;RoundTripperFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a real-world scenario, an auth RoundTripper can handle more complex tasks like using a refresh token to get an updated access token when it expires.&lt;/p&gt;

&lt;p&gt;Go's &lt;a href="https://pkg.go.dev/net/http#RoundTripper" rel="noopener noreferrer"&gt;documentation on RoundTripper says&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;RoundTrip should not attempt to handle higher-level protocol details such as redirects, authentication, or cookies.&lt;br&gt;
RoundTrip should not modify the request, except for consuming and closing the Request's Body&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;However, we'll ignore that here since it is overly-cautious and severely limits the RoundTripper's functionality. This is likely a constraint because the &lt;code&gt;*http.Request&lt;/code&gt; is a pointer and modifying it may have unexpected consequences. In production-ready RoundTrippers, &lt;code&gt;r.Clone(r.Context())&lt;/code&gt; can be used to create a new &lt;code&gt;*http.Request&lt;/code&gt; that is safe to modify.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sequential RoundTripper
&lt;/h3&gt;

&lt;p&gt;Similar to the &lt;code&gt;SequentialMiddleware&lt;/code&gt;, this is a helper that simplifies the composition of multiple RoundTrippers. It is especially useful with the &lt;code&gt;RoundTripperFunc&lt;/code&gt;. It also ensures that the last step in the RoundTripping is &lt;code&gt;http.DefaultTransport&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;SequentialRoundTripper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rts&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTripper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTripper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTripper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultTransport&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rt&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;slices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Backward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RoundTripperFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTrip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can use a few RoundTrippers like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;SequentialRoundTripper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;LogRoundTripper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CacheRoundTripper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;AuthRoundTripper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// easier to read than:&lt;/span&gt;
&lt;span class="n"&gt;LogRoundTripper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CacheRoundTripper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AuthRoundTripper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultTransport&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition to being easier to read and format, this can be used to create a slice of RoundTrippers based on configuration values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tests
&lt;/h2&gt;

&lt;p&gt;Now we have a toolkit of middleware and RoundTrippers that can be used for an HTTP server and a client. In order to demonstrate this, we can write some Go tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Correct Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestGood&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;SequentialMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;RecoveryMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;AuthMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;LoggerMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CacheMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;helloHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;httptest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SuccessfulResponse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewClientWithRoundTrippers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;LogRoundTripper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;CacheRoundTripper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;AuthRoundTripper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;doTestRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unexpected error: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;expectations&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;"Hello, World!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c"&gt;// request again to use client cache&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doTestRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unexpected error: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;expectations&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;            &lt;span class="s"&gt;"Hello, World!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ClientCacheUsed&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DisableClientCacheToDemoServerCache"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewClientWithRoundTrippers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;LogRoundTripper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;AuthRoundTripper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;doTestRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unexpected error: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;expectations&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;            &lt;span class="s"&gt;"Hello, World!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ServerCacheUsed&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ForbiddenWithoutAuthRoundTripper"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewClientWithRoundTrippers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;LogRoundTripper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;doTestRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unexpected error: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;expectations&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusForbidden&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;"Forbidden&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test starts by setting up the HTTP server with the middleware in the correct order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;RecoveryMiddleware&lt;/code&gt;: this is the first code to run. It defers &lt;code&gt;recover()&lt;/code&gt; to catch any &lt;code&gt;panic&lt;/code&gt; that happens down the line&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LoggerMiddleware&lt;/code&gt;: logs the request and response details. Running this second ensures that every request and response is logged, even ones that fail auth&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AuthMiddleware&lt;/code&gt;: before doing anything else, the request should be authenticated&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CacheMiddleware&lt;/code&gt;: finally, cache the response. Any requests that use a cached response will still be authenticated and logged&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next, a few different subtests are used to show different client scenarios. These all use the same general ordering, but exclude the cache or auth RoundTrippers to demonstrate different outcomes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;LogRoundTripper&lt;/code&gt;: always logs request/response&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CacheRoundTripper&lt;/code&gt;: cache the response. This is removed to demonstrate that the server-side cache also works&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AuthRoundTripper&lt;/code&gt;: adds the &lt;code&gt;Authorization&lt;/code&gt; header to the request. It is removed to show that the request fails without it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The output of this test shows that the client and server both log the request and response.&lt;/p&gt;

&lt;h3&gt;
  
  
  Incorrect Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestBad_ServerCacheExposesAccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// The server will cache responses before checking authentication. After caching a response, the next request&lt;/span&gt;
    &lt;span class="c"&gt;// will receive this response even if it does not authenticate successfully&lt;/span&gt;
    &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;SequentialMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;CacheMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;LoggerMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;AuthMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;RecoveryMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;helloHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;httptest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PopulateCacheWithAuth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewClientWithRoundTrippers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;LogRoundTripper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;AuthRoundTripper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;doTestRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unexpected error: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;expectations&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;"Hello, World!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c"&gt;// Server cache is used before AuthMiddleware, allowing unrestricted access&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"RequestWithoutAuth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewClientWithRoundTrippers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;LogRoundTripper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;doTestRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unexpected error: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;expectations&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;            &lt;span class="s"&gt;"Hello, World!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ServerCacheUsed&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this test, the middleware are initialized in reverse order. This has a few issues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;While the &lt;code&gt;RecoveryMiddleware&lt;/code&gt; will still handle panics in the main handler, it will not handle panics in any of the other middleware&lt;/li&gt;
&lt;li&gt;Since the &lt;code&gt;CacheMiddleware&lt;/code&gt; is the first to run, it will immediately respond with a cached response before checking auth. This means that unauthorized request will be able to access the server&lt;/li&gt;
&lt;li&gt;Additionally, since the &lt;code&gt;LoggerMiddleware&lt;/code&gt; runs after the cache, we won't even know that these unauthorized requests are occurring&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a trivial example, but we can see how a simple mistake like incorrect middleware order can have catatrophic results. Similar, but less-impactful errors can occur with the RoundTrippers. If we use the &lt;code&gt;AuthRoundTripper&lt;/code&gt; before the &lt;code&gt;CacheRoundTripper&lt;/code&gt;, our authentication credentials will be saved in the cache, potentially leaking sensitive details. Also, we will lose a benefit of the cache if the &lt;code&gt;AuthRoundTripper&lt;/code&gt; is refreshing access tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Middleware and RoundTrippers are simple and common tools in Go. While the topic is familiar for many seasoned Gophers, it's beneficial to revisit the basics and remember to carefully consider how they are used. These are just a few of the usecases and possible pitfalls of using middleware and RoundTrippers. Here are a few more possible ways to leverage them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automatic retries for 500-level errors (RoundTripper). Make sure to use this after the &lt;code&gt;CacheRoundTripper&lt;/code&gt; so you can use the cache before encountering and retrying any errors&lt;/li&gt;
&lt;li&gt;OpenTelemetry tracing and metrics (RoundTripper and middleware)&lt;/li&gt;
&lt;li&gt;Throttling and rate-limiting. Use with a middleware to protect your server from abuse, or implement in a RoundTripper to avoid overloading another server. Similar to the retries, this RoundTripper should run after the &lt;code&gt;CacheRoundTripper&lt;/code&gt; since the cache will already reduce the number of requests&lt;/li&gt;
&lt;li&gt;Client-side load balancing by choosing from a pool of upstream URLs&lt;/li&gt;
&lt;li&gt;Testing. The &lt;a href="https://github.com/dnaeon/go-vcr/tree/v4" rel="noopener noreferrer"&gt;&lt;code&gt;go-vcr&lt;/code&gt;&lt;/a&gt; library leverages a &lt;code&gt;RoundTripper&lt;/code&gt; to record interactions with external servers and then replay them using &lt;code&gt;httptest.Server&lt;/code&gt;. Make sure to run this RoundTripper before the &lt;code&gt;AuthRoundTripper&lt;/code&gt; so your &lt;code&gt;Authorization&lt;/code&gt; header isn't saved in a test fixture! Different tokens would also cause requests to not match&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let me know some of your favorite or novel usecases for middleware and RoundTrippers in Go!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/calvinmclean/calvinmclean.github.io/blob/main/examples/middleware-roundtripper" rel="noopener noreferrer"&gt;Check out the full code referenced in this article on Github!&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>go</category>
      <category>learning</category>
    </item>
    <item>
      <title>Improve Your Go Applications with Byte-level Data Optimization</title>
      <dc:creator>Calvin McLean</dc:creator>
      <pubDate>Fri, 09 May 2025 05:59:44 +0000</pubDate>
      <link>https://forem.com/calvinmclean/improve-your-go-applications-with-byte-level-data-optimization-1391</link>
      <guid>https://forem.com/calvinmclean/improve-your-go-applications-with-byte-level-data-optimization-1391</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/calvinmclean/calvinmclean.github.io/blob/main/examples/binary-data-optimization" rel="noopener noreferrer"&gt;Check out the full code referenced in this article on Github!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Almost any application requires a way to store or transfer data. We often choose formats like JSON or YAML that are easy to read and modify. These formats are built to be generic to work in a variety of usecases, which means they are not heavily optimized for data size or fast encoding/decoding.&lt;/p&gt;

&lt;p&gt;I recently started making a sourdough starter. This requires dividing, feeding, and discarding every 12 or 24 hours. To ensure accuracy and consistency, I record the time and amounts for each feeding. This data is just numbers and time (which is also a number). Since I already had data optimizations on my mind, I saw a real-world usecase for implementing binary data optimizations. My goal is to create a hyper-specific and optimized time-series data. Rather than defaulting to JSON for storing data in a file, I will store it in a binary format.&lt;/p&gt;

&lt;h2&gt;
  
  
  Intro to bytes
&lt;/h2&gt;

&lt;p&gt;Let's start with some basics about binary data in Go:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The most basic types here are &lt;code&gt;uint8&lt;/code&gt; and &lt;code&gt;byte&lt;/code&gt;, which are the same&lt;/li&gt;
&lt;li&gt;These contain 8 bits, which is 1 byte&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You might already be familiar with &lt;code&gt;byte&lt;/code&gt; and &lt;code&gt;[]byte&lt;/code&gt; from encoding/decoding JSON or reading/writing files. This data type is use by Go for any filesystem interactions or encoding/decoding data. With 8 bits, we have 2&lt;sup&gt;8&lt;/sup&gt; = 256 distinct values. Since we start at zero, we use the range 0-255. Each byte represents a number in this range and can be converted to characters from the &lt;a href="https://www.ascii-code.com" rel="noopener noreferrer"&gt;extended ASCII table&lt;/a&gt;. The lower-case latin alphabet starts at 97 for 'a' and ends at 122 for 'z'. Therefore, &lt;code&gt;[]byte{104, 101, 108, 108, 111}&lt;/code&gt; is "hello" and &lt;code&gt;'b' - 'a' == 1&lt;/code&gt; (&lt;a href="https://play.golang.com/p/CQLV93-yLf7" rel="noopener noreferrer"&gt;see it for yourself!&lt;/a&gt;). Instead of using the ASCII table to convert bytes into human-readable strings, we can assign our own meaning to the data.&lt;/p&gt;

&lt;p&gt;The following JSON data holds two numbers and is 20 characters long, so it is 20 bytes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;125&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A lot of this data is included just to make the data generically parseable by both humans and computers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;"&lt;/code&gt;: marks the start and end of the string data type&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:&lt;/code&gt;: separates keys from values&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;,&lt;/code&gt;: separates one key-value pair from another&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{&lt;/code&gt;/&lt;code&gt;}&lt;/code&gt;: shows the start and end of objects&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&lt;/code&gt; (space): just for human-readability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A lot of this data is not strictly necessary if we are able to make specific assumptions about the data. After removing the brackets, quotes, and colons, we are left with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a255b127
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now this is only 8 bytes. However, it is no longer a flexible format. In order to be parseable, we have to introduce a few constraints: keys are always alphabetic (a-z, A-Z) and values are numeric 0-9. These constraints allow distinguishing the keys from values when parsing the data instead of relying on commas, colons, and quotation marks. Even this simple reduction introduces significant restrictions on what the data can represent, which is why JSON is so verbose.&lt;/p&gt;

&lt;p&gt;These numbers are each 3 characters since they are meant to be human-readable, so they use up 3 bytes of data. You might have noticed that these are within the 0-255 range, so they can be represented by a single byte each. As a human-readable character from the ASCII table, 114 is &lt;code&gt;r&lt;/code&gt; and 125 is &lt;code&gt;}&lt;/code&gt;. If we are able to assume that 'a' and 'b' are in order, then we can represent this data with only 2 bytes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;r}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(&lt;a href="https://play.golang.com/p/i157FffBx8d" rel="noopener noreferrer"&gt;check it out here!&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;This data is no longer a generic key-value format and instead represents a specific type of data that just has two &lt;code&gt;uint8&lt;/code&gt; values. It also requires custom programming to encode and decode. In many usecases, these strict rules might not be worth the benefit. However, the tenfold reduction from 20 bytes to just 2 can result in significant storage and bandwidth savings.&lt;/p&gt;

&lt;p&gt;Now that we are familiar with &lt;code&gt;byte&lt;/code&gt; and &lt;code&gt;uint8&lt;/code&gt;, let's introduce a few more types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;uint16&lt;/code&gt;: this is two bytes and holds up to 2&lt;sup&gt;16&lt;/sup&gt;-1 (65,535)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;uint32&lt;/code&gt;: 4 bytes, 2&lt;sup&gt;32&lt;/sup&gt;-1 (4,294,967,295)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;uint64&lt;/code&gt;: 8 bytes, 2&lt;sup&gt;64&lt;/sup&gt;-1 (18,446,744,073,709,551,615)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We want to use the smallest type possible for each piece of data without compromising too much on the constraints. With such big jumps in size, there can be a lot of seemingly-wasted space. If we need a range of 0-300, we have to upsize from &lt;code&gt;uint8&lt;/code&gt; to &lt;code&gt;uint16&lt;/code&gt; which goes all the way to 65,535! It just uses one additional byte, so it's not a big deal. However, what if we could just use one additional &lt;em&gt;bit&lt;/em&gt; to reach 300 instead of a whole byte? This, of course, is entirely possible. It requires more complex parsing using bit shifting and masking, but we'll get more into that later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;In the previous section, we learned that we can significantly reduce the size of data by ditching JSON in favor of a more specific binary format. However, this requires a lot more knowledge about your data and the ability to follow strict constraints. It will increase the time it takes to program the encoding/decoding whenever new data fields are added. With JSON, you don't care if a number is &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;int64&lt;/code&gt;, &lt;code&gt;uint16&lt;/code&gt;, or any other value. In our case, these differences are very important and decisions between them must be data-driven. Let's get back to the sourdough use case and define some requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sourdough starter measurement&lt;/strong&gt;: this is the amount of existing starter that we are using. The process is started with just flour and water, so this measurement starts at 0. Once the process gets started, I'll be using 25-50 grams of starter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flour measurement&lt;/strong&gt;: we will always have a non-zero amount of flour, generally 50-100 grams.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Water measurement&lt;/strong&gt;: we will also always have non-zero amount of water. I aim to use the same amount of flour and water, but am sometimes off by a few grams so I can't always assume it's the same.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flour type&lt;/strong&gt;: we can feed the starter with different types of flour: whole wheat, all purpose, bread, rye, or others&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Datetime&lt;/strong&gt;: we want to know the time of feeding. Since feeding is a process that takes some time, we aren't going to pinpoint the time down to the second. Truncating to the minute is plenty. We could even do 5 minute granularity if that buys us any additional optimization. I don't need to backfill historical data, so dates after 2025 are fine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that we have some base requirements, we can start designing and optimizing our data format.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measurements
&lt;/h3&gt;

&lt;p&gt;Since our measurements all have similar constraints and numeric values are easy, let's start there.&lt;/p&gt;

&lt;p&gt;I follow a low-waste recipe that uses small amounts of each ingredient, so we can easily use &lt;code&gt;uint8&lt;/code&gt; which maxes out at 255 grams. If we need to allow for a larger-scale bakery to use this data format, we can measure in decagrams or kilograms which gives us up to 2,550 or 255,000 grams but loses some granularity. A &lt;code&gt;uint16&lt;/code&gt; would give us a much larger range at the cost of 1 additional byte, so this is an easy change to make if the requirements for measurements were different. In this case, let's stick with &lt;code&gt;uint8&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Can we simplify it further? Ratios are big in the world of sourdough baking, so maybe we can use them to reduce data sizes. Unfortunately, since we start with 0 starter and non-zero water/flour, a ratio won't work here. Water and flour are usually in a 1:1 or 1:2 ratio which could easily be stored with a single bit. However, this doesn't account for inaccuracy in measurements. I often add a little too much water or a little too much flour so the values are slightly different and I want to record that discrepancy. Additionally, I might want to experiment with different ratios like 1:1.5 or others, so this constraint doesn't fit.&lt;/p&gt;

&lt;p&gt;Since we are already using 1 byte with the &lt;code&gt;uint8&lt;/code&gt;, any additional reductions introduce a lot of complexity. Taking off a single bit gives us up to 127g, which is enough for the scale I am operating at, but 1 bit isn't worth the effort and results in the same on-disk space in the end since filesystems operate at the byte-level. Removing another bit and limiting our measurements to 63g still works for my very small scale, but sometimes I might want to make a larger batch of bread that doesn't fit in this constraint. Therefore, I'll stick with the &lt;code&gt;uint8&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In code, this looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dataSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// encode to bytes&lt;/span&gt;
&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StarterGrams&lt;/span&gt;
&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FlourGrams&lt;/span&gt;
&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaterGrams&lt;/span&gt;

&lt;span class="c"&gt;// decode bytes&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StarterGrams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FlourGrams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaterGrams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Flour type
&lt;/h3&gt;

&lt;p&gt;Starters can be fed with different types of flour which will impact how it grows. I am only aware of four relevant types: all purpose, whole wheat, bread, rye, but I am sure there are more. In Go, enums are often created using &lt;code&gt;iota&lt;/code&gt; constants, which is an untyped integer. Each flour type can be assigned to a different integer value, so the type used will determine the range of values.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;uint8&lt;/code&gt; allows up to 255 which goes way beyond the five types. 2&lt;sup&gt;3&lt;/sup&gt; has the range 0-7 which fits our 5 types, but is not very extensible if I learn about and start using new types. However, Go does not allow having variables or data smaller than a byte and neither do filesystems. We would have to pad out the remaining 5 bits with zeros and lose any benefit from the optimization. Therefore, we'll stick with the &lt;code&gt;uint8&lt;/code&gt;, but keep 2&lt;sup&gt;3&lt;/sup&gt; in mind.&lt;/p&gt;

&lt;p&gt;Once again, the code is very simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// encode&lt;/span&gt;
&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FlourType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// decode&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FlourType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FlourType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Time
&lt;/h3&gt;

&lt;p&gt;Go represents time with a struct that can easily be converted to seconds since an epoch (January 1, 1970 UTC). This is an &lt;code&gt;int64&lt;/code&gt; which is already compact at 8 bytes, but we can do better. 1,745,010,295 represents a timestamp in April 2025 which is less than the max &lt;code&gt;uint32&lt;/code&gt; (4,294,967,295). Since we don't care about seconds, we can convert this to minutes by dividing by 60: 29,083,504. This is already way smaller, but doesn't fit in our next size down (&lt;code&gt;uint16&lt;/code&gt;). Since we don't need historical data, we can count the minutes since 2025 as the epoch instead of 1970. That 55 year difference converts to 28,908,000 minutes. While this is a large number, it is less than 1% of &lt;code&gt;uint32&lt;/code&gt;'s max. This optimization would just extend our max date by 55 years which is not important here.&lt;/p&gt;

&lt;p&gt;Since this data is sequential in a time-series, we can optimize by using time offsets. The initial time is recorded using &lt;code&gt;uint32&lt;/code&gt; (4 bytes) and then each new data point stores an amount of time passed since the previous. The options for recording the offsets are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;uint8&lt;/code&gt;: maxes at 255 minutes, or 10.625 hours which is not enough for a 12-24 hour feeding interval&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;uint16&lt;/code&gt;: significantly larger range of 0-65,535 minutes, or about 45 days&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;uint16&lt;/code&gt; is a good choice since the starter can go for a week or more without feeding if it's refrigerated. There is a low risk of overflowing the 45 days, but it's not an excessively large limit.&lt;/p&gt;

&lt;p&gt;The flaw with this implementation is that we always have to read our data from the beginning if we want to see the latest date. Also, the first entry is a different format which can be challenging to deal with. There is no way to randomly-access data with this optimization. This would significantly reduce the data size from 4 bytes to only 2, so it may be worth it, especially since parsing the data should be fast. This flaw could also be mitigated by saving data ranges in chunks, where a full timestamp is written after every N data points. This would be interesting to implement, but I am focusing on parsing individual data points for now.&lt;/p&gt;

&lt;p&gt;There's one more option that I am considering. Take a look at this simple datetime: &lt;code&gt;2025-04-16 12:00&lt;/code&gt;. Breaking it down, we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Year (2025+)&lt;/strong&gt;: This is the largest number, but we do not plan to insert historical data, so we can assume it starts at 2025 and only record an offset from there. Using a &lt;code&gt;uint8&lt;/code&gt; gives us 255 years before it overflows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Month (1-12)&lt;/strong&gt;: Our smallest data type, &lt;code&gt;uint8&lt;/code&gt; goes up to 255 which is way overkill. Half of that, 2&lt;sup&gt;4&lt;/sup&gt;, allows 0-15, so we can use 4 bits instead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day (1-31)&lt;/strong&gt;: Once again, &lt;code&gt;uint8&lt;/code&gt; is overkill for this. 2&lt;sup&gt;5&lt;/sup&gt; allows 0-31 so it's a perfect fit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hour (1-24)&lt;/strong&gt;: This is another case for 2&lt;sup&gt;5&lt;/sup&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minute (0-60)&lt;/strong&gt;: This one needs 2&lt;sup&gt;6&lt;/sup&gt;, which is 0-63&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overall, we have 8 + 4 + 5 + 5 + 6 = 28 bits. This is another partial-byte idea that requires padding out to 32 bits/4 bytes anyways. Instead of padding with empty bits, the 3-bit option for flour type could be stored in those extra bits. This requires more complex bitwise operations to encode and decode, so I'll put it on the backburner for now.&lt;/p&gt;

&lt;p&gt;Ultimately, I'll move forward with the simplest option first: &lt;code&gt;uint32&lt;/code&gt; for a minutes epoch instead of the usual seconds epoch. This approach has a significant edge for its simplicity and already halves the data size compared to &lt;code&gt;int64&lt;/code&gt;. Keep the other ideas in mind for future optimizations.&lt;/p&gt;

&lt;p&gt;Since we have multiple bytes now, the code is a bit different:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// encode&lt;/span&gt;
&lt;span class="c"&gt;// convert to minutes since epoch, then write across 4 bytes&lt;/span&gt;
&lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unix&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;
&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LittleEndian&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PutUint32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kt"&gt;uint32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c"&gt;// decode&lt;/span&gt;
&lt;span class="c"&gt;// read 4 bytes into a uint32 and convert back to epoch time&lt;/span&gt;
&lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LittleEndian&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Uint32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Putting it all together
&lt;/h3&gt;

&lt;p&gt;Time: 4 bytes (32 bits)&lt;br&gt;
Measurements: 1 byte each, for a total of 3 bytes (24 bits)&lt;br&gt;
FlourType: 1 byte (8 bits)&lt;br&gt;
&lt;strong&gt;Total&lt;/strong&gt;: 8 bytes (64 bits)&lt;/p&gt;

&lt;p&gt;This conveniently fits in a single &lt;code&gt;int64&lt;/code&gt;! However, &lt;code&gt;[8]byte&lt;/code&gt; is more practical and efficient to work with. If I feed my sourdough starter twice daily for 100 years, that's just 570.3 KiB!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Two data points per day is 2 * 365 = 730 records each year&lt;/li&gt;
&lt;li&gt;Each record is 8 bytes total, so 8 * 730 = 5,840 bytes per year&lt;/li&gt;
&lt;li&gt;After 100 years, its 584,000 bytes&lt;/li&gt;
&lt;li&gt;584,000 / 1,024 ~= 570.3 KiB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I averaged out the size of 100,000 JSON data points to be about 97.71074 bytes each. The JSON representation of this data would be 730 * 100 * 97.71074 = 7,132,884.02 bytes ~= 6.8 MiB! While this data is still pretty small, it significantly larger than the binary representation of data.&lt;/p&gt;

&lt;p&gt;Here is the final code responsible for these optimizations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;dataSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Data&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Time&lt;/span&gt;         &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;
    &lt;span class="n"&gt;StarterGrams&lt;/span&gt; &lt;span class="kt"&gt;uint8&lt;/span&gt;
    &lt;span class="n"&gt;FlourGrams&lt;/span&gt;   &lt;span class="kt"&gt;uint8&lt;/span&gt;
    &lt;span class="n"&gt;WaterGrams&lt;/span&gt;   &lt;span class="kt"&gt;uint8&lt;/span&gt;
    &lt;span class="n"&gt;FlourType&lt;/span&gt;    &lt;span class="n"&gt;FlourType&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sd&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;MarshalBinary&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dataSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// The uint8 fields can be directly insert to the byte slice&lt;/span&gt;
    &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StarterGrams&lt;/span&gt;
    &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FlourGrams&lt;/span&gt;
    &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaterGrams&lt;/span&gt;
    &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FlourType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Convert Unix time to minutes&lt;/span&gt;
    &lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unix&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;

    &lt;span class="c"&gt;// The encoding/binary library can be used to insert the uint32&lt;/span&gt;
    &lt;span class="c"&gt;// time representation into the byte slice&lt;/span&gt;
    &lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LittleEndian&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PutUint32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kt"&gt;uint32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;UnmarshalBinary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Simply read uint8 fields from the byte slice in the correct order&lt;/span&gt;
    &lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StarterGrams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FlourGrams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaterGrams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FlourType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FlourType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="c"&gt;// Get uint32 minutes from the byte slice and convert back into time.Time&lt;/span&gt;
    &lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LittleEndian&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Uint32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar to marshal and unmarshal in &lt;code&gt;encoding/json&lt;/code&gt;, implementing &lt;code&gt;MarshalBinary&lt;/code&gt; and &lt;code&gt;UnmarshalBinary&lt;/code&gt; enables encoding and decoding with the &lt;code&gt;encoding/gob&lt;/code&gt; package. This is useful when a struct is part of another data structure, such as a slice. With these implemented, we can encode and decode binary data with the same convenience as JSON.&lt;/p&gt;

&lt;p&gt;Go's built-in benchmarking provides a way to test the performance compared to JSON. In order to make the tests more fair, the &lt;code&gt;init&lt;/code&gt; function is used to generate a slice of data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;sourdough&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="n"&gt;_000&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="n"&gt;sourdough&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="m"&gt;2025&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Intn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;          &lt;span class="c"&gt;// Year: 2025 + up to 49 years&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Month&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Intn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c"&gt;// Month&lt;/span&gt;
            &lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Intn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;28&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c"&gt;// Day&lt;/span&gt;
            &lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Intn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c"&gt;// Hour&lt;/span&gt;
            &lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Intn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c"&gt;// Minute&lt;/span&gt;
            &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StarterGrams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Intn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;256&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FlourGrams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Intn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;256&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaterGrams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Intn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;256&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FlourType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sourdough&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FlourType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Intn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sourdough&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FlourTypeBread&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The benchmarks for each implementation encode the data to bytes, then decode it back into a new slice of data. Finally, it is compared to the initial data to make sure it encoded and decoded successfully. Here are the results of the benchmark:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; go test -bench . -benchmem
goos: darwin
goarch: arm64
pkg: sourdough
cpu: Apple M3 Pro
BenchmarkJSON-11                            50          23172882 ns/op   5747196 B/op      30054 allocs/op
BenchmarkBinaryUnixMinute-11        1000000000           0.01411 ns/op         0 B/op          0 allocs/op
BenchmarkBinaryUnix-11              1000000000           0.01398 ns/op         0 B/op          0 allocs/op
BenchmarkBinaryCompact-11           1000000000           0.01439 ns/op         0 B/op          0 allocs/op
PASS
ok      sourdough   2.433s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first number in each benchmark row is the total number of times that the test was executed. By default, Go will limit the benchmark time to 1 second and run as many iterations of each test as it takes to fill this time. It looks like each of the binary tests max out at 1,000,000,000 iterations while the JSON test only runs a miniscule 50 times. The next value shows how many nanoseconds each iteration of the benchmark took to execute, which shows a clear advantage to the binary implementations. Finally, we can see the memory usage and allocations for the tests which is another big win for binary.&lt;/p&gt;

&lt;p&gt;There are slight variances each time the benchmark is executed, so I encourage you to try it for yourself.&lt;/p&gt;

&lt;p&gt;In this output, there are a few additional tests different binary implementations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;BenchmarkBinaryUnixMinute&lt;/code&gt; uses &lt;code&gt;uint32&lt;/code&gt; for minutes since the epoch&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BenchmarkBinaryUnix&lt;/code&gt; uses &lt;code&gt;uint64&lt;/code&gt; for seconds since the epoch (&lt;code&gt;time.Unix()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BenchmarkBinaryCompact&lt;/code&gt; uses a more compact encoding that we'll explore in the next section&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The benchmark results for the different binary implementations are too close to call, so comparing these will come down to storage size rather than performance.&lt;/p&gt;

&lt;p&gt;Overall, each of the binary implementations is way faster than JSON, uses less memory, and much less storage space. In the short test execution time, the binary benchmarks had 20,000,000 times more iterations than the JSON benchmark. To be clear, that's 20,000,000 &lt;em&gt;times&lt;/em&gt; the number of iterations, not 20,000,000 &lt;em&gt;more&lt;/em&gt; iterations. Then, each JSON operation takes 23,172,882 nanoseconds, or about 23ms. The binary operations are taking less than a nanosecond, which is incomprehensible and 1,639,977,495 times faster than the JSON. We can scale things up a bit and run 1000 executions of encoding for each implementation. This takes over 9 seconds for JSON and about 477 milliseconds for the binary data. 9 seconds is definitely a loading time that you notice, while 477ms is more tolerable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimizing it further
&lt;/h2&gt;

&lt;p&gt;Benchmarking already revealed that this doesn't have an impact on speed, but let's look back at the partial-byte optimizations for time (28 bits) and flour type (3 bits). Speed isn't everything, so we'll focus on how this can reduce the data size. Since there are 31 bits, we can extend the flour type to use 4 bits and have an even 32 (4 bytes). Now the &lt;code&gt;uint32&lt;/code&gt; size that previously held the time can hold up to 16 different flour types!&lt;/p&gt;

&lt;p&gt;This reduces the overall size of data to 7 bytes instead of 8. This small difference is more significant than it initially seems. The 12.5% reduction in size results in 71.3 KiB savings over 100 years. More importantly, it's an interesting technique to learn about and implement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sharing a byte
&lt;/h3&gt;

&lt;p&gt;The tricky part now is implementing the shared bytes.&lt;/p&gt;

&lt;p&gt;4 bytes look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0x00 0x00 0x00 0x00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zoomed in a bit more:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00000000 00000000 00000000 00000000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need to assign some meaning to these bits.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 00000000 0000 0000 0 00000 00 0000 0000
|   A    | B  | C    |  D  |   E   | F  |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A&lt;/strong&gt; - Year offset (8 bits, 0-255): this is the entire first byte&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;B&lt;/strong&gt; - Month (4 bits, 0-15): this is half of the second byte&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;C&lt;/strong&gt; - Day (5 bits, 0-31): the other half of the second byte + one bit from the third byte&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;D&lt;/strong&gt; - Hour (5 bits, 0-31): the next five bits of the third byte&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;E&lt;/strong&gt; - Minute (6 bits, 0-63): two remaining bits of the third byte and first half of the last byte&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;F&lt;/strong&gt; - Flour Type (4 bits, 0-15): last four bits of the fourth byte&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is how we can access data within the same byte, or even as parts of multiple bytes. When you have a normal 8-bit &lt;code&gt;uint8&lt;/code&gt;, it may look something like this when converted to binary representation: &lt;code&gt;11110000&lt;/code&gt;. This is the base ten number 240. This byte can be split into two parts (nibbles): &lt;code&gt;1111&lt;/code&gt; and &lt;code&gt;0000&lt;/code&gt;, or 15 and 0 respectively. How can we extract these values? In Go, the smallest integer is &lt;code&gt;uint8&lt;/code&gt;, so these would still have to be represented by 8 bits: &lt;code&gt;00001111&lt;/code&gt; and &lt;code&gt;00000000&lt;/code&gt;. This is where bit shifting and masking come in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;11110000&lt;/code&gt; can be changed to &lt;code&gt;00001111&lt;/code&gt; by shifting the 1's to the right by 4 positions:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="kt"&gt;uint8&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;b11110000&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;11110000&lt;/code&gt; can be changed to &lt;code&gt;00000000&lt;/code&gt; by replacing the 1's with 0's by masking&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="kt"&gt;uint8&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;b11110000&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;b00001111&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;ul&gt;
&lt;li&gt;This works by performing a bitwise "and" operation on the data. We put a &lt;code&gt;1&lt;/code&gt; in the positions that we want to keep since &lt;code&gt;1 &amp;amp; 1 == 1&lt;/code&gt; and &lt;code&gt;1 &amp;amp; 0 == 0&lt;/code&gt;. Anywhere on the original data with a 0 will remain 0, and anywhere with a 1 will remain 1&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;If we want the middle 4 bits (&lt;code&gt;1100&lt;/code&gt;, or 12), we need to combine shifting and masking:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="kt"&gt;uint8&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;b11110000&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;b00_1111_00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;ul&gt;
&lt;li&gt;This works by "selecting" the middle 4 bits with the mask, and then shifts them over to be the right-most bits&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://play.golang.com/p/6sgOaIfO4E5" rel="noopener noreferrer"&gt;Check out this simple example&lt;/a&gt;. This isn't intended to be an in-depth tutorial of bitwise operations. If this is a new concept for you, I encourage you to continue researching and learning about it.&lt;/p&gt;

&lt;p&gt;Now we need to apply these techniques to extract six different pieces of data from four bytes.&lt;/p&gt;

&lt;p&gt;Here's how we can encode the datetime and flour type into four bytes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;encodeCompactDateAndFlourType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sd&lt;/span&gt; &lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Year is simply one byte/uint8&lt;/span&gt;
    &lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Year&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="m"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Month is the left four bits of the 2nd byte&lt;/span&gt;
    &lt;span class="n"&gt;month&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Month&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Day is 5 bits split across the 2nd and 3rd bytes&lt;/span&gt;
    &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Day&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;dayPart1&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;b000_1111_0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;dayPart2&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;b0000000_1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;

    &lt;span class="c"&gt;// Hour is 5 bits in the middle of the 3rd byte&lt;/span&gt;
    &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hour&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;

    &lt;span class="c"&gt;// Minute is 6 bits split across the 3rd and 4th bytes&lt;/span&gt;
    &lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Minute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;minutePart1&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;b00_11_0000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
    &lt;span class="n"&gt;minutePart2&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;b0000_1111&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;

    &lt;span class="c"&gt;// FlourType is just the final 4 bits&lt;/span&gt;
    &lt;span class="n"&gt;flourType&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FlourType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;b0000_1111&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;month&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;dayPart1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;dayPart2&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;minutePart1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;minutePart2&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;flourType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can then be decoded from bytes with this function, which simply reverses the previous bitwise operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;decodeCompactDateAndFlourType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sd&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="m"&gt;2025&lt;/span&gt;

    &lt;span class="n"&gt;month&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;

    &lt;span class="n"&gt;dayPart1&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;b000_1111_0&lt;/span&gt;
    &lt;span class="n"&gt;dayPart2&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;b0000000_1&lt;/span&gt;
    &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dayPart1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;dayPart2&lt;/span&gt;

    &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;b000_11111&lt;/span&gt;

    &lt;span class="n"&gt;minutePart1&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;b00_11_0000&lt;/span&gt;
    &lt;span class="n"&gt;minutePart2&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;b0000_1111&lt;/span&gt;
    &lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;minutePart1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;minutePart2&lt;/span&gt;

    &lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Month&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FlourType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FlourType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;b0000_1111&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this code isn't the most complex, it's generally unapproachable for a lot of developers. Even when revisiting this to write about it, I had to spend some extra time figuring out how it works. All of this saves just one byte of data. At a large scale, this can save a decent amount of storage space, but comes at a cost of maintainability.&lt;/p&gt;

&lt;p&gt;The previous solution that operated at the byte-level already provided a significant boost in performance and reduced storage space with a minimal impact to maintainability. It's usually not worth the effort to take this next step to the bit level. If there's a specific use case to store many 4-bit values, that is a case where bit-level operations would be simple and halves the data size compared to &lt;code&gt;uint8&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this exploration of data optimization, we learned about the different data types and how they can be manipulated to represent more than just basic numbers. Well-defined constraints are the key to optimizing data. If we know exactly what our data can look like and how it needs to be used, we can make informed decisions about how to represent that data.&lt;/p&gt;

&lt;p&gt;The tradeoff is that this format is very rigid and challenging to modify. Also, it cannot be parsed outside of our program, so debugging and analyzing data becomes more difficult.&lt;/p&gt;

&lt;p&gt;Now what if you wanted to balance flexibility, extensibility, and performance? This is the reason Protobuf exists. It uses a schema/definition for structured data and generates the code for encoding and decoding it. This allows the defined types to be compatible with different progamming languages and spares engineers from low-level programming. I created a proto definition for this data and added another benchmark. There are a few small tradeoffs here. First, the smallest integer is &lt;code&gt;uint32&lt;/code&gt;, so the protobuf data isn't quite as compact as the custom solution. Additionally, it is slightly slower in the benchmark, but when we are looking at thousandths of a nanosecond, the difference isn't significant.&lt;/p&gt;

&lt;p&gt;If you have the ability to use rigidly-structured data, I recommend using Protobuf before rolling your own custom binary solution. However, it's still good to learn about how it works from scratch, and you can still squeeze out more optimizations if it's really necessary. Low-level concepts like binary data and bytes can be challenging to work with, but understanding them can increase your understanding of how programs work and how memory impacts performance. Knowing how things work will give you more tools and options for improving performance and efficiency of your applications.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/calvinmclean/calvinmclean.github.io/blob/main/examples/binary-data-optimization" rel="noopener noreferrer"&gt;Check out the full code and tests on Github!&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>tutorial</category>
      <category>learning</category>
      <category>programming</category>
    </item>
    <item>
      <title>Effortless HTTP Client Testing in Go</title>
      <dc:creator>Calvin McLean</dc:creator>
      <pubDate>Thu, 11 Jul 2024 23:16:03 +0000</pubDate>
      <link>https://forem.com/calvinmclean/effortless-http-client-testing-in-go-4d75</link>
      <guid>https://forem.com/calvinmclean/effortless-http-client-testing-in-go-4d75</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;As a software engineer, you are probably familiar with writing code to interact with external HTTP services. After all, it is one of the most common things we do! Whether it's fetching data, processing payments with a provider, or automating social media posts, our applications almost always involve external HTTP requests. In order for our software to be reliable and maintainable, we need a way to test the code responsible for executing these requests and handling the errors that could occur. This leaves us with a few options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implement a client wrapper that can be mocked by the main application code, which still leaves a gap in testing&lt;/li&gt;
&lt;li&gt;Test response parsing and handling separate from actual request execution. While it's probably a good idea to test this lower-level unit individually, it'd be nice if that could easily be covered along with the actual requests&lt;/li&gt;
&lt;li&gt;Move tests to integration testing which can slow down development and is unable to test some error scenarios and may be impacted by the reliability of other services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These options aren't terrible, especially if they can all be used together, but we have a better option: VCR testing.&lt;/p&gt;

&lt;p&gt;VCR testing, named after the videocassette recorder, is a type of mock testing that generates &lt;a href="https://en.wikipedia.org/wiki/Test_fixture" rel="noopener noreferrer"&gt;test fixtures&lt;/a&gt; from actual requests. The fixtures record the request and response to automatically reuse in future tests. Although you might have to modify the fixtures afterwards to handle dynamic time-based inputs or remove credentials, it is much simpler than creating mocks from scratch. There are a few additional benefits to VCR testing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Execute your code all the way down to the HTTP level, so you can test your application end-to-end&lt;/li&gt;
&lt;li&gt;You can take real-world responses and modify the generated fixtures to increase response time, cause rate limiting, etc. to test error scenarios that don't often occur organically&lt;/li&gt;
&lt;li&gt;If your code uses an external package/library for interacting with an API, you might not know exactly what a request and response look like, so VCR testing can automatically figure that out&lt;/li&gt;
&lt;li&gt;Generated fixtures can also be used for debugging tests and making sure your code executes the expected request&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deeper Dive using Go
&lt;/h2&gt;

&lt;p&gt;Now that you see the motivation behind VCR testing, let's dig deeper into how to implement it in Go using &lt;a href="https://github.com/dnaeon/go-vcr" rel="noopener noreferrer"&gt;&lt;code&gt;dnaeon/go-vcr&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This library integrates seamlessly into any HTTP client code. If your client library code doesn't already allow setting the &lt;code&gt;*http.Client&lt;/code&gt; or the Client's &lt;code&gt;http.Transport&lt;/code&gt;, you should add that now.&lt;/p&gt;

&lt;p&gt;For those that aren't familiar, an &lt;code&gt;http.Transport&lt;/code&gt; is an implementation of &lt;code&gt;http.RoundTripper&lt;/code&gt;, which is basically a client-side middleware that can access the request/response. It is useful for implementing automatic retries on 500-level or 429 (rate-limit) responses, or adding metrics and logging around requests. In this case, it allows &lt;code&gt;go-vcr&lt;/code&gt; to re-reoute requests to its own in-process HTTP server.&lt;/p&gt;

&lt;h3&gt;
  
  
  URL Shortener Example
&lt;/h3&gt;

&lt;p&gt;Let's get started on a simple example. We want to create a package that makes requests to the free &lt;a href="https://cleanuri.com" rel="noopener noreferrer"&gt;https://cleanuri.com&lt;/a&gt; API. This package will provide one function: &lt;code&gt;Shorten(string) (string, error)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Since this is a free API, maybe we can just test it by making requests directly to the server? This might work, but can result in a few problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The server has a rate limit of 2 requests/second which could be an issue if we have a lot of tests&lt;/li&gt;
&lt;li&gt;If the server goes down or takes awhile to respond, our tests could fail&lt;/li&gt;
&lt;li&gt;Although the shortened URLs are cached, we have no guarantee that we will get the same output every time&lt;/li&gt;
&lt;li&gt;It's just rude to send unnecessary traffic to a free API!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ok, what if we create an interface and mock it? Our package is incredibly simple, so this would overcomplicate it. Since the lowest-level thing we use is &lt;code&gt;*http.Client&lt;/code&gt;, we would have to define a new interface around it and implement a mock.&lt;/p&gt;

&lt;p&gt;Another option is to override the target URL to use a local port served by &lt;code&gt;httptest.Server&lt;/code&gt;. This is basically a simplified version of what &lt;code&gt;go-vcr&lt;/code&gt; does and would be sufficient in our simple case, but won't be maintainable in more complex scenarios. Even in this example, you'll see how managing generated fixtures is easier than managing different mock server implementations.&lt;/p&gt;

&lt;p&gt;Since our interface is already defined and we know some valid input/output from trying the UI at &lt;a href="https://cleanuri.com" rel="noopener noreferrer"&gt;https://cleanuri.com&lt;/a&gt;, this is a great opportunity to practice &lt;a href="https://dev.to/calvinmclean/test-driven-api-development-in-go-1fb8"&gt;test-driven development&lt;/a&gt;. We'll start by implementing a simple test for our &lt;code&gt;Shorten&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;shortener_test&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestShorten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;shortened&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;shortener&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shorten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://dev.to/calvinmclean"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unexpected error: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;shortened&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"https://cleanuri.com/7nPmQk"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unexpected result: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shortened&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty easy! We know that the test will fail to compile because &lt;code&gt;shortener.Shorten&lt;/code&gt; is not defined, but we run it anyways so fixing it will be more satisfying.&lt;/p&gt;

&lt;p&gt;Finally, let's go ahead and implement this function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;shortener&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;DefaultClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultClient&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://cleanuri.com/api/v1/shorten"&lt;/span&gt;

&lt;span class="c"&gt;// Shorten will returned the shortened URL&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Shorten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targetURL&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;DefaultClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PostForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Values&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"url"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;targetURL&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unexpected response code: %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;respData&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ResultURL&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"result_url"`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewDecoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;respData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;respData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResultURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now our test passes! It's just as satisfying as I promised.&lt;/p&gt;

&lt;p&gt;In order to start using VCR, we need to initialize the Recorder and override &lt;code&gt;shortener.DefaultClient&lt;/code&gt; at the beginning of the test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestShorten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;recorder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fixtures/dev.to"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NoError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;recorder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModeRecordOnce&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Recorder should be in ModeRecordOnce"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;shortener&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetDefaultClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the test to generate &lt;code&gt;fixtures/dev.to.yaml&lt;/code&gt; with details about the test's request and response. When we re-run the test, it uses the recorded response instead of reaching out to the server. Don't just take my word for it; turn off your computer's WiFi and re-run the tests!&lt;/p&gt;

&lt;p&gt;You might also notice that the time it takes to run the test is relatively consistent since &lt;code&gt;go-vcr&lt;/code&gt; records and replays the response duration. You can manually modify this field in the YAML to speed up the tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mocking Errors
&lt;/h3&gt;

&lt;p&gt;To further demonstrate the benefits of this kind of testing, let's add another feature: retry after &lt;code&gt;429&lt;/code&gt; response due to rate-limiting. Since we know the API's rate limit is per second, &lt;code&gt;Shorten&lt;/code&gt; can automatically wait a second and retry if it receives a &lt;code&gt;429&lt;/code&gt; response code.&lt;/p&gt;

&lt;p&gt;I tried to reproduce this error using the API directly, but it seems like it responds with existing URLs from a cache before considering the rate limit. Rather than polluting the cache with bogus URLs, we can create our own mocks this time.&lt;/p&gt;

&lt;p&gt;This is a simple process since we already have generated fixtures. After copy/pasting &lt;code&gt;fixtures/dev.to.yaml&lt;/code&gt; to a new file, duplicate the successful request/response interaction and change first response's code from &lt;code&gt;200&lt;/code&gt; to &lt;code&gt;429&lt;/code&gt;. This fixture mimics a successful retry after rate-limiting failure.&lt;/p&gt;

&lt;p&gt;The only difference between this test and the original test is the new fixture filename. The expected output is the same since &lt;code&gt;Shorten&lt;/code&gt; should handle the error. This means we can throw the test in a loop to make it more dynamic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestShorten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fixtures&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"fixtures/dev.to"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"fixtures/rate_limit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fixture&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;fixtures&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;recorder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NoError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;}()&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;recorder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModeRecordOnce&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Recorder should be in ModeRecordOnce"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;shortener&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetDefaultClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="n"&gt;shortened&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;shortener&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shorten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://dev.to/calvinmclean"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unexpected error: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;shortened&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"https://cleanuri.com/7nPmQk"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unexpected result: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shortened&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once again, the new test fails. This time due to the unhandled &lt;code&gt;429&lt;/code&gt; response, so let's implement the new feature to pass the test. In order to maintain simplicity, our function handles the error using &lt;code&gt;time.Sleep&lt;/code&gt; and a recursive call rather than dealing with the complexity of considering max retries and exponential backoffs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Shorten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targetURL&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// ...&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusTooManyRequests&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Shorten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targetURL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unexpected response code: %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run the tests again and see them pass!&lt;/p&gt;

&lt;p&gt;Take it a step further on your own and try adding a test for a bad request, which will occur when using an invalid URL like &lt;code&gt;my-fake-url&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The full code for this example (and the bad request test) is available &lt;a href="https://github.com/calvinmclean/calvinmclean.github.io/blob/main/examples/go-vcr-testing/shortener_test.go" rel="noopener noreferrer"&gt;on Github&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The benefits of VCR testing are clear from just this simple example, but they are even more impactful when dealing with complex applications where the requests and responses are unwieldy. Rather than dealing with tedious mocks or opting for no tests at all, I encourage you to give this a try in your own applications. If you already rely on integration tests, getting started with VCR is even easier since you already have real requests that can generate fixtures.&lt;/p&gt;

&lt;p&gt;Check out more documentation and examples in the package's Github repository: &lt;a href="https://github.com/dnaeon/go-vcr" rel="noopener noreferrer"&gt;https://github.com/dnaeon/go-vcr&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>testing</category>
      <category>tutorial</category>
      <category>learning</category>
    </item>
    <item>
      <title>Test-driven API Development in Go</title>
      <dc:creator>Calvin McLean</dc:creator>
      <pubDate>Mon, 08 Jul 2024 23:36:15 +0000</pubDate>
      <link>https://forem.com/calvinmclean/test-driven-api-development-in-go-1fb8</link>
      <guid>https://forem.com/calvinmclean/test-driven-api-development-in-go-1fb8</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Test-driven development is an effective method for ensuring well-tested and refactorable code. The basic idea is that you start development by writing tests. These tests clearly document expectations and create a rubric for a successful implementation. When done properly, you can clearly define the expected input/output of a function before writing any code. This has a few immediate benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You carefully consider the interface for interacting with your code and design it to be testable&lt;/li&gt;
&lt;li&gt;When you begin writing code, your flow isn't interrupted by manual testing or stepping through execution logic to predict the outcome. Instead, you just run the tests&lt;/li&gt;
&lt;li&gt;Making a test pass becomes a goal that is satisfying to achieve. Breaking down the process into a series of well-defined and achieveable milestones makes the work more enjoyable&lt;/li&gt;
&lt;li&gt;Avoid post-implementation laziness and over-confidence that could prevent you from testing your code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that you're convinced of the benefits, you can get started with test-driven development (TDD) by following these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write or modify tests&lt;/li&gt;
&lt;li&gt;Check if test fails&lt;/li&gt;
&lt;li&gt;Write the minimum amount of code to make tests pass&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These steps are followed in a cycle so you are always adding more tests to challenge the current implementation.&lt;/p&gt;

&lt;p&gt;The last step, which specifies writing the minimum amount of code, is where things can get tedious if followed rigidly. It's important to understand why this rule exists before you can determine when it's appropriate to stray from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simple Example
&lt;/h2&gt;

&lt;p&gt;You're tasked with implementing the function &lt;code&gt;Add(x, y int) int&lt;/code&gt;. Before you jump to the implementation and just &lt;code&gt;return x + y&lt;/code&gt;, write the simplest test: &lt;code&gt;1 + 1 == 2&lt;/code&gt;. Then, what is the simplest implementation that would pass the test? It's just &lt;code&gt;return 2&lt;/code&gt;. Now your tests pass!&lt;/p&gt;

&lt;p&gt;At this point, you realize that you need more tests, so you pick up the pace and add a few more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1 + 2 == 3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;100 + 5 == 105&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now your tests fail, so you need to fix the implementation. You can't just &lt;code&gt;return 3&lt;/code&gt; or &lt;code&gt;return 105&lt;/code&gt; this time, so you need to find a solution that works for all tests. This leads to the implementation: &lt;code&gt;return x + y&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;While this feels overly tedious in the trivial example, strict adherence to this method caused you to write multiple tests instead of just trusting your implementation. Of course, your initial idea to &lt;code&gt;return x + y&lt;/code&gt; would have worked, but the point is to re-train yourself to rely on tests rather than your own understanding of the code. In the real world, you're not the only one working on this piece of code and will inevitably forget implementation details. This process forces you to write more tests and think of more ways to break the simple implementation.&lt;/p&gt;

&lt;p&gt;Eventually, you'll gain experience and learn to find the balance that works in the different scenarios that you encounter. You'll get back to full-speed implementation of features and find that you have fewer bugs and write more maintanable code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step by step TDD for an HTTP API
&lt;/h2&gt;

&lt;p&gt;Let's get into a more complicated example using TDD for an HTTP REST API. This step-by-step guide uses my Go framework, &lt;a href="https://github.com/calvinmclean/babyapi" rel="noopener noreferrer"&gt;&lt;code&gt;babyapi&lt;/code&gt;&lt;/a&gt;, but the concepts can be applied anywhere.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;babyapi&lt;/code&gt; uses generics to create a full CRUD API around Go structs, making it super easy to create a full REST API and client CLI. In addition to this, the &lt;code&gt;babytest&lt;/code&gt; package provides some tools for creating end-to-end API tables tests. Using TDD at the API-level allows for fully testing the HTTP and storage layers of a new API or feature all at once.&lt;/p&gt;

&lt;p&gt;Disclaimer: Since &lt;code&gt;babyapi&lt;/code&gt; handles most of the implementation and also is used to generate test boilerplate, we aren't technically starting with TDD. However, we'll see how beneficial it is when adding support for &lt;code&gt;PATCH&lt;/code&gt; requests to our API.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a new Go project&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create initial &lt;code&gt;main.go&lt;/code&gt; using &lt;a href="https://github.com/calvinmclean/babyapi/blob/main/examples/simple/main.go" rel="noopener noreferrer"&gt;&lt;code&gt;babyapi&lt;/code&gt;'s simple example&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use the CLI to generate a &lt;a href="https://gist.github.com/calvinmclean/16fcc97d8e9f2fe30b8d0f7c44243a24" rel="noopener noreferrer"&gt;test boilerplate&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Implement each test by filling in the placeholders with expected JSON&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run the tests and see that they pass!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Since &lt;code&gt;PUT&lt;/code&gt; is idempotent, it requires all fields to be included. To avoid this, we want to add support for toggling &lt;code&gt;Completed&lt;/code&gt; with &lt;code&gt;PATCH&lt;/code&gt; requests. We start by adding a simple test for what we expect this feature to look like&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This test fails since &lt;code&gt;babyapi&lt;/code&gt; doesn't support &lt;code&gt;PATCH&lt;/code&gt; by default. We can fix it by implementing &lt;code&gt;Patch&lt;/code&gt; for the &lt;code&gt;TODO&lt;/code&gt; struct. Since we defined our feature with two tests, our simplest implementation isn't just setting &lt;code&gt;Completed = true&lt;/code&gt; and we have to use the value from the request&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now we can change the &lt;code&gt;Completed&lt;/code&gt; status of a &lt;code&gt;TODO&lt;/code&gt;, but we still cannot use &lt;code&gt;PATCH&lt;/code&gt; to modify other fields as show by this new set of tests&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update &lt;code&gt;Patch&lt;/code&gt; to set the remaining fields&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Our tests still fail since we always update the &lt;code&gt;TODO&lt;/code&gt; with the request fields, even if they're empty. Fix this by updating the implementation to check for empty values&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The new &lt;code&gt;UpdateWithPatch&lt;/code&gt; test passes, but our previous tests fail. Since we changed &lt;code&gt;Completed&lt;/code&gt; to be &lt;code&gt;*bool&lt;/code&gt;, &lt;code&gt;TODO&lt;/code&gt;s created with an empty value will show as &lt;code&gt;null&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Implement &lt;code&gt;Render&lt;/code&gt; for &lt;code&gt;TODO&lt;/code&gt; so we can treat &lt;code&gt;nil&lt;/code&gt; as &lt;code&gt;false&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Implementing the &lt;code&gt;PATCH&lt;/code&gt; feature with test-driven development resulted in a robust set of tests and a well-implemented feature. Since we started by defining the expected input and output of a &lt;code&gt;PATCH&lt;/code&gt; request in tests, it was easy to see the issues caused by not checking for empty values in the request. Also, our pre-existing tests were able to protect from breaking changes when changing the type of &lt;code&gt;Completed&lt;/code&gt; to &lt;code&gt;*bool&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Test-driven development is an effective approach for creating fully tested and correct code. By starting with tests in mind, we can ensure that every piece of code is designed to be testable instead of letting tests be an afterthought.&lt;/p&gt;

&lt;p&gt;If you're hesitant about adopting TDD, here are a few ideas to get started:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Try it in simple scenarios where a function's input/output is clear and the implementation is not overly complicated. You can write a robust table test for the variety of input/output that could be encountered. Having a clear visual of the different scenarios can simplify implementation&lt;/li&gt;
&lt;li&gt;If you're fixing a new bug, you have already identified a gap in your testing. Start by writing a test that would have identified this bug in the first place. Then, make this test pass without breaking any existing tests.&lt;/li&gt;
&lt;li&gt;Similar to the &lt;code&gt;babyapi&lt;/code&gt; example, you can use TDD for high-level API tests. Once you have a definition of the expected request/response, you can resume your usual development flow for more detail-oriented parts of the implementation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if TDD isn't a good fit for the way you write code, it's still a powerful tool to have in your belt. I encourage you to at least commit some time to trying it out and see how it affects your development process.&lt;/p&gt;

</description>
      <category>go</category>
      <category>testing</category>
      <category>tutorial</category>
      <category>tdd</category>
    </item>
    <item>
      <title>Let's Talk About REST...</title>
      <dc:creator>Calvin McLean</dc:creator>
      <pubDate>Wed, 01 May 2024 02:19:37 +0000</pubDate>
      <link>https://forem.com/calvinmclean/lets-talk-about-rest-1kjl</link>
      <guid>https://forem.com/calvinmclean/lets-talk-about-rest-1kjl</guid>
      <description>&lt;p&gt;REST, or Representational State Transfer, is a concept that guides the implementation of an application's interface. It is typically used in HTTP communication with JSON data to define create, read, update and delete (CRUD) operations on resources. However, by definition, REST APIs can exist without JSON and even without HTTP. Incorrectly claiming that &lt;em&gt;any&lt;/em&gt; HTTP JSON API as REST is harmful to the standard since the assumptions about the API's implementation might not be true. In addition to this, non-standard APIs require extensive documentation in order to be usable. If we correctly implement REST and use accurate terminology when discussing APIs, we can build applications that are much easier to use and maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basics of REST
&lt;/h2&gt;

&lt;p&gt;Roy Fielding defined the term REST in his &lt;a href="https://ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm"&gt;1999-2000 PhD dissertation&lt;/a&gt;. Fundamentally, REST requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Client-server architecture&lt;/li&gt;
&lt;li&gt;Statelessness between requests&lt;/li&gt;
&lt;li&gt;Uniform interface&lt;/li&gt;
&lt;li&gt;Identifiable resources&lt;/li&gt;
&lt;li&gt;Hypermedia representations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first few requirements are often achieved using HTTP methods (&lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;) and URIs (Uniform Resource Identifiers) as the uniform interface and identification of resources. This clear and consistent interaction builds confidence in which resource is affected by the request and what that effect is. The hypermedia can be JSON, HTML, or anything else that can provide a consistent representation of the application’s resources. &lt;/p&gt;

&lt;p&gt;As we dig deeper into REST, the examples will describe an HTTP and JSON API for the sake of familiarity and wide-appeal, but keep in mind that these are not defining characteristics of REST.&lt;/p&gt;

&lt;h2&gt;
  
  
  REST API Maturity
&lt;/h2&gt;

&lt;p&gt;To better commmunicate the level of RESTfulness that an HTTP API achieves, the &lt;a href="https://martinfowler.com/articles/richardsonMaturityModel.html"&gt;Richardson maturity model&lt;/a&gt; describes four levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Level 0&lt;/em&gt;: Use a URI and POST for client-server interaction&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Level 1&lt;/em&gt;: Use separate URIs for each resource&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Level 2&lt;/em&gt;: Introduce more HTTP methods for different operations on resources. Follow established standards for the behavior of each method&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Level 3&lt;/em&gt;: Use hypermedia&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice that we are now including HTTP-specifics like methods and URIs into the requirements since this maturity model is specific for REST APIs on the web, not the more general definition of REST.&lt;/p&gt;

&lt;p&gt;Level 2 is the most impactful level since it introduces a lot of functionality and consistency to the API. It also dictates the use of standard HTTP response codes for communicating the success or failure of requests, such as &lt;code&gt;200 OK&lt;/code&gt; and &lt;code&gt;400 Bad Request&lt;/code&gt;. Once your API reaches this level, it demonstrates a fully-functioning application that allows for all necessary interactions between the client and the server. In most cases, this level of API maturity is sufficient, but what does it look like to take it one step further?&lt;/p&gt;

&lt;p&gt;The last, and often overlooked, piece is hypermedia linking, or Hypertext As The Engine of Application State (HATEOAS). Despite the horrendous acronym, this level introduces sophistacation and friendliness to an already-useful API. By adding links to the responses, the server creates a self-documenting API where the client is free to navigate the entire interface using the base path and reasonable assumptions about HTTP methods.&lt;/p&gt;

&lt;p&gt;For example, if we are navigating an API for blog posts, we can start with a request to &lt;code&gt;GET /posts&lt;/code&gt;. If the API is RESTful, it will respond with a list of Post resources containing details and a link to &lt;code&gt;/posts/{id}&lt;/code&gt;. The server will only provide the client with Posts that it has permission to access, so the client can follow the link to &lt;code&gt;GET /posts/1&lt;/code&gt;, which will respond with details of the Post and, ideally, links to more related resources such as Likes and Comments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Post Title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"links"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/posts/1/comments"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"likes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/posts/1/likes"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see from this example, the basic knowledge of the &lt;code&gt;/posts&lt;/code&gt; endpoint and the &lt;code&gt;GET&lt;/code&gt; method is all the client needs to fully navigate the Posts API. A level-2 API would not enable the client to discover the Comments or Likes related to a Post. If you look beyond the unpronounceable acronym, HATEOAS is an essential feature of a robust REST API.&lt;/p&gt;

&lt;p&gt;This example uses the more-familiar JSON API, but the same thing can be achieved using HTML as the hypermedia. In this case, the hypermedia would reveal the child resources by actual clickable links or UI elements instead of just the URIs. Implementations of REST clearly go beyond structural JSON representations of objects and instead show us more generally how to interact with an application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self Documenting
&lt;/h2&gt;

&lt;p&gt;You can create a very functional API without level 3, but only the root-level resources will be available to clients without some sort of external documentation or brute-force search. In most cases, this is achieved using an OpenAPI specification.&lt;/p&gt;

&lt;p&gt;OpenAPI specifications are a generic and expressive way to document APIs, but they are tedious to write and keep in sync with actual API behavior. The extra step of maintaining API specifications on top of writing code and managing other responsibilities allows the implementation and specification to drift apart. It is worth considering if we can skip this step altogether when our APIs are intuitive and standardized.&lt;/p&gt;

&lt;p&gt;Query parameters and request headers are overlooked by self-documenting REST principles. OpenAPI Specifications come out ahead in this aspect, but self-documenting APIs can still be very usable if they adhere to a few assumptions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A resource's root endpoint (like &lt;code&gt;GET /posts&lt;/code&gt;) can filter/search by any field of a Post, like title or author&lt;/li&gt;
&lt;li&gt;There might be more fields that aid in querying like &lt;code&gt;order&lt;/code&gt; and &lt;code&gt;limit&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;User-friendly errors will make troubleshooting easy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before you start deleting all of your API specifications, remember every scenario has unique considerations. We can't realistically expect to learn every API by trial-and-error, so external documentation is still valuable. Thoughtful implementation of REST builds a solid foundation for a usable and well-documented application. Then, API specifications are a valuable add-on to provide more clarity about the API. However, the foundations of REST come first and a specification is not an excuse for eschewing standards. Ultimately, you should find a balance that works for you, your peers, and users.&lt;/p&gt;

&lt;h2&gt;
  
  
  babyapi
&lt;/h2&gt;

&lt;p&gt;Now this is the part where I tell you about a library I created, &lt;a href="https://github.com/calvinmclean/babyapi"&gt;&lt;code&gt;babyapi&lt;/code&gt;&lt;/a&gt;, and how it makes all of this tedious REST stuff easy!&lt;/p&gt;

&lt;p&gt;I created babyapi with the goal of providing the &lt;a href="https://dev.to/calvinmclean/the-easiest-way-to-create-a-rest-api-with-go-20bo"&gt;simplest path to a full REST API&lt;/a&gt;. It started as a humble package that used generics to reduce code duplication in my &lt;a href="https://github.com/calvinmclean/automated-garden"&gt;&lt;code&gt;automated-garden&lt;/code&gt;&lt;/a&gt; API. After breaking it out into its own repository, I added the optional &lt;code&gt;HATEOAS&lt;/code&gt; extension which sets up automatic hypermedia linking. Now you can easily create a REST API which achieves all levels in the Richardson maturity model using &lt;code&gt;babyapi&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/calvinmclean/babyapi/tree/main/examples/nested"&gt;Check it out&lt;/a&gt; and let me know what you think!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;REST is a useful standard for creating intuitive application interfaces, especially in the case of HTTP web applications. In modern web development, the understood definition of REST has drifted to just mean any JSON HTTP interface that loosely interacts with resources. In reality, these are not adhering to the strict definitions of REST and claiming RESTfulness is harmful to usability and maintainability of the APIs. While API specifications are a powerful tool for describing complex APIs, I hope you will first consider designing a self-documenting REST API. This will enable your users to quickly get started with your API before getting bogged down by specifications. Of course, specifications still have an appropriate role in many complex APIs, but we should not be relying on them to make an API usable in the first place.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>softwaredevelopment</category>
      <category>learning</category>
    </item>
    <item>
      <title>Run Go + HTMX in the Cloud with Acorn</title>
      <dc:creator>Calvin McLean</dc:creator>
      <pubDate>Mon, 18 Dec 2023 02:45:53 +0000</pubDate>
      <link>https://forem.com/calvinmclean/run-go-htmx-in-the-cloud-with-acorn-5egd</link>
      <guid>https://forem.com/calvinmclean/run-go-htmx-in-the-cloud-with-acorn-5egd</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Note: Since writing this article, the Acorn SaaS product has been shutdown.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/calvinmclean/how-to-build-a-web-application-with-htmx-and-go-3183"&gt;a recent article&lt;/a&gt;, I demonstrated how &lt;a href="https://github.com/calvinmclean/babyapi" rel="noopener noreferrer"&gt;&lt;code&gt;babyapi&lt;/code&gt;&lt;/a&gt;, a library I created, makes it easy to write a TODO app with a RESTful API and HTMX frontend using only 150 lines of code. &lt;code&gt;babyapi&lt;/code&gt; abstracts the HTTP handling based on a provided struct and serves HTMX templates for a dynamic frontend.&lt;/p&gt;

&lt;p&gt;It works great in a tutorial, but you might have been left thinking: "what about persistent storage and running in the cloud?"&lt;/p&gt;

&lt;p&gt;This simple tutorial will show you how to connect your &lt;code&gt;babyapi&lt;/code&gt; application to Redis storage and quickly run in the cloud using the &lt;a href="https://docs.acorn.io/sandbox" rel="noopener noreferrer"&gt;free Sandbox&lt;/a&gt; from &lt;a href="https://www.acorn.io" rel="noopener noreferrer"&gt;Acorn&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storage
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;babyapi.Storage&lt;/code&gt; interface and the &lt;code&gt;SetStorage&lt;/code&gt; modifier allow implementing any storage backend for your application. As mentioned in the previous article, the &lt;code&gt;babyapi/storage&lt;/code&gt; package provides a generic implementation of the interface with helpers for setting up local file or Redis storage. This time, since the goal is to run in the cloud rather than just locally, we'll use the Redis version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRedisDB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;":6379"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Password&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error setting up redis storage: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"TODO"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The full example code for this tutorial is available in the &lt;a href="https://github.com/calvinmclean/babyapi/blob/main/examples/todo-htmx/main.go" rel="noopener noreferrer"&gt;&lt;code&gt;babyapi&lt;/code&gt; GitHub repository&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With this simple addition, the TODO application is ready to connect to a Redis instance and run in the cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Acorn
&lt;/h2&gt;

&lt;p&gt;If you're not already familiar with Acorn, I recommend checking out &lt;a href="https://docs.acorn.io" rel="noopener noreferrer"&gt;the official docs&lt;/a&gt; to learn more about it! Basically, it is an app platform that makes it easy to deploy cloud applications and their dependencies by describing them in a simple Acornfile. Instead of configuring all of the required Kubernetes manifests to run our application in the cloud, we can just use an Acornfile.&lt;/p&gt;

&lt;p&gt;First, we need a &lt;a href="https://github.com/calvinmclean/babyapi/blob/main/examples/todo-htmx/Dockerfile" rel="noopener noreferrer"&gt;Dockerfile&lt;/a&gt; to create our app container. Then, instead of building and pushing this image to a container registry and writing a Kubernetes manifests (by copy/pasting from an online example if we're being honest), let's take a look at Acorn.&lt;/p&gt;

&lt;p&gt;Before deploying the updated TODO app, we need the Redis database dependency. If you search "run redis in k8s", all of the top results look exhausting. Alternatively, the Acorn &lt;a href="https://docs.acorn.io/databases/redis" rel="noopener noreferrer"&gt;documentation for Redis&lt;/a&gt; looks much simpler.&lt;/p&gt;

&lt;p&gt;We can even use the Acornfile from the documentation's example with one little change to build and run the TODO app with Redis database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services: db: {
    image: "ghcr.io/acorn-io/redis:v7.#.#-#"
}

containers: app: {
    build: {
        context: "."
    }
    consumes: ["db"]
    ports: publish: "8080/http"
    env: {
        REDIS_HOST: "@{service.db.address}"
        REDIS_PASS: "@{service.db.secrets.admin.token}"
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not only did this save us the effort of configuring everything for the TODO app, we even have the entire Redis dependency with a persistent volume and a random password. This is already way better than the no-volume, no-password K8s manifests I would thrown together for this example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it for real
&lt;/h2&gt;

&lt;p&gt;Now that you have seen how it all works, you can run it for yourself using this button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://acorn.io/run/ghcr.io/calvinmclean/babyapi-htmx-acorn?ref=calvinmclean" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Facorn.io%2Fv1-ui%2Frun%2Fbadge%3Fimage%3Dghcr.io%2Bcalvinmclean%2Bbabyapi-htmx-acorn%26ref%3Dcalvinmclean%26style%3Dfor-the-badge%26color%3Dbrightgreen" alt="Run in Acorn"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the time of posting this, Acorn offers free Sandbox accounts to anyone with a GitHub account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcalvinmclean%2Fcalvinmclean.github.io%2Fmain%2Farticles%2Fbabyapi-htmx-acorn%2Facorn_ui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcalvinmclean%2Fcalvinmclean.github.io%2Fmain%2Farticles%2Fbabyapi-htmx-acorn%2Facorn_ui.png" alt="Acorn UI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once it has finished deploying, open the endpoint and append &lt;code&gt;/todos&lt;/code&gt; in the URL to reveal the HTMX UI! You can even use the &lt;code&gt;babyapi&lt;/code&gt; CLI from your terminal to create a new TODO and watch it show up in the UI automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ACORN_ADDR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://COPY_ENDPOINT_FROM_ACORN

go run &lt;span class="nt"&gt;-mod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mod &lt;span class="se"&gt;\&lt;/span&gt;
  github.com/calvinmclean/babyapi/examples/todo-htmx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-address&lt;/span&gt; &lt;span class="nv"&gt;$ACORN_ADDR&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  post TODOs &lt;span class="s1"&gt;'{"title": "use babyapi on Acorn!"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The availability of app platforms like Acorn and easy-to-use libraries like &lt;a href="https://github.com/calvinmclean/babyapi" rel="noopener noreferrer"&gt;&lt;code&gt;babyapi&lt;/code&gt;&lt;/a&gt; take care of the boring and tedious parts of software engineering and let you focus on the things that interest you. Try them out for yourself and let me know what you have any feature requests or issues with &lt;code&gt;babyapi&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>go</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>cloud</category>
    </item>
    <item>
      <title>How To Build a Web Application with HTMX and Go</title>
      <dc:creator>Calvin McLean</dc:creator>
      <pubDate>Tue, 12 Dec 2023 04:56:11 +0000</pubDate>
      <link>https://forem.com/calvinmclean/how-to-build-a-web-application-with-htmx-and-go-3183</link>
      <guid>https://forem.com/calvinmclean/how-to-build-a-web-application-with-htmx-and-go-3183</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcalvinmclean%2Fcalvinmclean.github.io%2Fmain%2Farticles%2Fbabyapi-htmx%2Fui-demo.gif" class="article-body-image-wrapper"&gt;&lt;img alt="sorry darkmode users" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcalvinmclean%2Fcalvinmclean.github.io%2Fmain%2Farticles%2Fbabyapi-htmx%2Fui-demo.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This UI and backend are implemented with only 150 lines of code, including the HTML!&lt;br&gt;
The full example code for this tutorial is available in the &lt;a href="https://github.com/calvinmclean/babyapi/blob/main/examples/todo-htmx/main.go" rel="noopener noreferrer"&gt;&lt;code&gt;babyapi&lt;/code&gt; GitHub repository&lt;/a&gt; if you're eager to get into it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In my recent article, &lt;a href="https://dev.to/calvinmclean/the-easiest-way-to-create-a-rest-api-with-go-20bo"&gt;The Easiest Way to Create a REST API With Go&lt;/a&gt;, I demonstrated how &lt;a href="https://github.com/calvinmclean/babyapi" rel="noopener noreferrer"&gt;&lt;code&gt;babyapi&lt;/code&gt;&lt;/a&gt; can jumpstart REST API creation. This time, I will walk you through some additional &lt;code&gt;babyapi&lt;/code&gt; features and show how to create an easy and dynamic frontend using &lt;a href="https://htmx.org" rel="noopener noreferrer"&gt;HTMX&lt;/a&gt;. If you're not already familiar, HTMX is a library that basically extends HTML with functionality that normally requires Javascript.&lt;/p&gt;

&lt;p&gt;HTMX was designed with REST backends in mind, which makes it a perfect companion for &lt;code&gt;babyapi&lt;/code&gt;. HTMX provides a snappy SPA-like feel by replacing and rendering individual components from server responses instead of the entire page.&lt;/p&gt;

&lt;p&gt;Since &lt;code&gt;babyapi&lt;/code&gt; uses &lt;code&gt;chi/render&lt;/code&gt; package for requests and responses, it automatically supports input from HTML forms instead of only JSON. Additionally, &lt;code&gt;babyapi&lt;/code&gt; defines an &lt;code&gt;HTMLer&lt;/code&gt; interface which allows resources to define custom HTML string responses when the client requests &lt;code&gt;text/html&lt;/code&gt;. We can take advantage of these features to easily create and serve an HTMX frontend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the TODOs API
&lt;/h2&gt;

&lt;p&gt;In the last article, we created a simple TODO resource. This struct allows &lt;code&gt;babyapi&lt;/code&gt; to serve the necessary HTTP endpoints for interacting with the data:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"github.com/calvinmclean/babyapi"&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;TODO&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;babyapi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultResource&lt;/span&gt;

    &lt;span class="n"&gt;Title&lt;/span&gt;       &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Description&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Completed&lt;/span&gt;   &lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;babyapi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewAPI&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;
        &lt;span class="s"&gt;"TODOs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/todos"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TODO&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunCLI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In order to add an HTMX UI on top of this, all we need to do is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Implement &lt;code&gt;babyapi.HTMLer&lt;/code&gt; interface for the &lt;code&gt;TODO&lt;/code&gt; resource&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;api.SetGetAllResponseWrapper&lt;/code&gt; with a new &lt;code&gt;babyapi.HTMLer&lt;/code&gt; type to render HTML for the &lt;code&gt;/todos&lt;/code&gt; response&lt;/li&gt;
&lt;li&gt;Write templates for HTMX frontend with these features:

&lt;ul&gt;
&lt;li&gt;List all TODOs in a table&lt;/li&gt;
&lt;li&gt;Buttons to mark items as completed and delete&lt;/li&gt;
&lt;li&gt;Server-sent events automatically append new TODOs&lt;/li&gt;
&lt;li&gt;Form to create TODOs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Respond With HTML Instead of JSON
&lt;/h2&gt;

&lt;p&gt;By default, &lt;code&gt;babyapi&lt;/code&gt; is designed to marshal structs to the requested response type (usually JSON). HTML responses are not as straightforward, so &lt;code&gt;babyapi&lt;/code&gt; provides an &lt;code&gt;HTMLer&lt;/code&gt; interface that enables creating HTML responses from resource structs:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;HTMLer&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;HTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;HTML(*http.Request)&lt;/code&gt; method can be implemented as follows:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;todoRowTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;`...`&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;HTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;tmpl&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Must&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"todoRow"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todoRowTemplate&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;babyapi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustRenderHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmpl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This method simply renders a template from a string and uses &lt;code&gt;babyapi.MustRenderHTML&lt;/code&gt; to execute it with the TODO data.&lt;/p&gt;

&lt;p&gt;The HTML template looks like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;tr&lt;/span&gt; &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"this"&lt;/span&gt; &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"outerHTML"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;{{ .Title }}&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;{{ .Description }}&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;{{ $disabled := "" }}{{ if .Completed }}{{ $disabled = "disabled" }}{{ end }}&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
            &lt;span class="na"&gt;hx-put=&lt;/span&gt;&lt;span class="s"&gt;"/todos/{{ .ID }}"&lt;/span&gt;
            &lt;span class="na"&gt;hx-headers=&lt;/span&gt;&lt;span class="s"&gt;'{"Accept": "text/html"}'&lt;/span&gt;
            &lt;span class="na"&gt;hx-include=&lt;/span&gt;&lt;span class="s"&gt;"this"&lt;/span&gt;
            &lt;span class="err"&gt;{{&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="na"&gt;disabled&lt;/span&gt; &lt;span class="err"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

            &lt;span class="c"&gt;&amp;lt;!-- Include entire TODO item for idempotent PUT --&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Completed"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Title"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"{{ .Title }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Description"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"{{ .Description }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ID"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"{{ .ID }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            Complete
        &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-delete=&lt;/span&gt;&lt;span class="s"&gt;"/todos/{{ .ID }}"&lt;/span&gt; &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"swap:1s"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            Delete
        &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: some minor details, like CSS classes, are excluded from the example for simplicity. The full example can be found &lt;a href="https://github.com/calvinmclean/babyapi/blob/main/examples/todo-htmx/main.go" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This template creates an HTML table row (&lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt;) to display the TODO's title and description. The row also contains buttons to mark the item as complete or to delete it.&lt;/p&gt;

&lt;p&gt;On top of the regular HTML, we use HTMX attributes to control interactions with the backend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;hx-target&lt;/code&gt; and &lt;code&gt;hx-swap&lt;/code&gt; tell HTMX to replace the entire row with the contents of a successful response. These attributes are inherited by the buttons&lt;/li&gt;
&lt;li&gt;The "Complete" button uses &lt;code&gt;hx-put&lt;/code&gt; to make a &lt;code&gt;PUT&lt;/code&gt; request to &lt;code&gt;/todos/{{ .ID }}&lt;/code&gt;. The request sets &lt;code&gt;Accept: text/html&lt;/code&gt; to request an HTML response from the server. &lt;code&gt;hx-include="this"&lt;/code&gt; uses the child &lt;code&gt;input&lt;/code&gt; fields to make the request body. This sets &lt;code&gt;Completed=true&lt;/code&gt; and leaves the other fields unchanged&lt;/li&gt;
&lt;li&gt;The "Delete" button uses &lt;code&gt;hx-delete&lt;/code&gt; to send a &lt;code&gt;DELETE&lt;/code&gt; request to &lt;code&gt;/todos/{{ .ID }}&lt;/code&gt;. Then, &lt;code&gt;hx-swap="swap:1s"&lt;/code&gt; tells HTMX to swap new contents over 1 second instead of immediately. Since the response content will be empty, this creates a fade-out effect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that we have a simple HTML implementation, run the server, create a TODO item, and fetch it:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

go build &lt;span class="nt"&gt;-o&lt;/span&gt; todo-app

&lt;span class="c"&gt;# start the server in a separate terminal&lt;/span&gt;
./todo-app serve

&lt;span class="c"&gt;# create new TODO&lt;/span&gt;
./todo-app post TODOs &lt;span class="s1"&gt;'{"title": "use babyapi!"}'&lt;/span&gt;

&lt;span class="c"&gt;# get the previously-created TODO by ID with HTML response&lt;/span&gt;
./todo-app &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: text/html"&lt;/span&gt; get TODOs clnvnt5o402av6j1oal0


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Loading &lt;code&gt;http://localhost:8080/todos/{ID}&lt;/code&gt; in the web browser will show the browser-rendering of the HTML, but it's not very impressive yet without style or HTMX working. The next step will be rendering the full HTML page to display all TODOs and enable HTMX.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the All TODOs Page
&lt;/h2&gt;

&lt;p&gt;Logically, the UI view for all TODOs will come from our base endpoint, &lt;code&gt;/todos&lt;/code&gt;. However, &lt;code&gt;babyapi&lt;/code&gt; is setup to use the default response type of &lt;code&gt;*babyapi.ResourceList[*TODO]&lt;/code&gt; to create a JSON-compatible response. As you probably guessed, &lt;code&gt;babyapi&lt;/code&gt; has a way to deal with this.&lt;/p&gt;

&lt;p&gt;All we have to do is use &lt;code&gt;api.SetGetAllResponseWrapper&lt;/code&gt; to set a function that accepts &lt;code&gt;[]*TODO&lt;/code&gt; and returns a new &lt;code&gt;render.Renderer&lt;/code&gt;. For this example, we create the &lt;code&gt;AllTODOs&lt;/code&gt; type to satisfy the &lt;code&gt;render.Renderer&lt;/code&gt; and &lt;code&gt;babyapi.HTMLer&lt;/code&gt; interfaces:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;AllTODOs&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TODO&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;AllTODOs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;allTODOsTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;`...`&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;AllTODOs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;HTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;tmpl&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Must&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"todoRow"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todoRowTemplate&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tmpl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Must&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;tmpl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"allTODOs"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allTODOsTemplate&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;babyapi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustRenderHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmpl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;babyapi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewAPI&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="s"&gt;"TODOs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/todos"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TODO&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetGetAllResponseWrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todos&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Renderer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;AllTODOs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;AllTODOs&lt;/code&gt; type implements &lt;code&gt;babyapi.HTMLer&lt;/code&gt; in the same way as the previous example: just parse and execute HTML templates. Then,  &lt;code&gt;api.SetGetAllResponseWrapper&lt;/code&gt; function simply returns &lt;code&gt;AllTODOs&lt;/code&gt; from the provided slice of TODOs.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;allTODOs&lt;/code&gt; template contains the full HTML page which imports HTMX scripts and UIKit CSS. The &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; portion of this new template looks like:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;table&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;thead&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Title&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Description&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;tbody&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;hx-post=&lt;/span&gt;&lt;span class="s"&gt;"/todos"&lt;/span&gt; &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"none"&lt;/span&gt; &lt;span class="na"&gt;hx-on::after-request=&lt;/span&gt;&lt;span class="s"&gt;"this.reset()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Title"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Description"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Add TODO&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

            {{ range .Items }}
            {{ template "todoRow" . }}
            {{ end }}
        &lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: some styling and other details that are not important to the example are excluded for brevity. The full example can be found &lt;a href="https://github.com/calvinmclean/babyapi/blob/main/examples/todo-htmx/main.go" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This template creates the structure of an HTML table and populates the first row with a simple HTML form for creating new TODO items. Then, the &lt;code&gt;todoRow&lt;/code&gt; template from the previous section is used to create rows for all existing TODOs.&lt;/p&gt;

&lt;p&gt;The form uses &lt;code&gt;hx-post="/todos"&lt;/code&gt; to send a &lt;code&gt;POST&lt;/code&gt; request with the form contents to the API. &lt;code&gt;hx-swap="none"&lt;/code&gt; disables swapping the response contents since we will use server-sent events in the next section to append new rows. Then we use &lt;code&gt;hx-on::after-request="this.reset()"&lt;/code&gt; to reset the form.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implement Server-Sent Events
&lt;/h2&gt;

&lt;p&gt;Server-sent events allow one-way communication from the backend to the frontend. In this case, we will use the feature to push new TODO rows to the UI, even if they are created from the CLI or other sources. Luckily, both &lt;code&gt;babyapi&lt;/code&gt; and HTMX make it super easy to use server-sent events.&lt;/p&gt;

&lt;p&gt;HTMX has an &lt;a href="https://htmx.org/extensions/server-sent-events/" rel="noopener noreferrer"&gt;SSE extension&lt;/a&gt; which can be used in this UI by replacing the plain &lt;code&gt;&amp;lt;tbody&amp;gt;&lt;/code&gt; with the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;tbody&lt;/span&gt;
    &lt;span class="na"&gt;hx-ext=&lt;/span&gt;&lt;span class="s"&gt;"sse"&lt;/span&gt;
    &lt;span class="na"&gt;sse-connect=&lt;/span&gt;&lt;span class="s"&gt;"/todos/listen"&lt;/span&gt;
    &lt;span class="na"&gt;sse-swap=&lt;/span&gt;&lt;span class="s"&gt;"newTODO"&lt;/span&gt;
    &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"beforeend"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    ...
&lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This new &lt;code&gt;tbody&lt;/code&gt; works with server-sent events using the new HTMX attributes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;hx-ext="sse"&lt;/code&gt; enables the extension&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sse-connect="/todos/listen"&lt;/code&gt; opens the connection to the server's SSE endpoint (not implemented yet)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sse-swap="newTODO"&lt;/code&gt; instructs HTMX to only use events with the type &lt;code&gt;newTODO&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hx-swap="beforeend"&lt;/code&gt; will append rows before the &lt;code&gt;&amp;lt;/tbody&amp;gt;&lt;/code&gt; closing tag&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that the frontend is ready to receive new TODO rows, the backend just needs to send them! &lt;code&gt;babyapi&lt;/code&gt; makes it unbelievably simple to handle SSE connections, even if this concept is completely new to you. The &lt;code&gt;api.AddServerSentEventHandler&lt;/code&gt; modifier adds a new route to handle SSE connections and returns a channel for sending events.&lt;/p&gt;

&lt;p&gt;Since we want to push new TODOs after they are created, we can use &lt;code&gt;api.SetOnCreateOrUpdate&lt;/code&gt; to push events to the channel after successful &lt;code&gt;POST&lt;/code&gt; requests:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="n"&gt;todoChan&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddServerSentEventHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/listen"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetOnCreateOrUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;babyapi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MethodPost&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;todoChan&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;babyapi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServerSentEvent&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"newTODO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This function first makes sure the request method is &lt;code&gt;POST&lt;/code&gt; and then pushes a new event on the channel. The event has the &lt;code&gt;newTODO&lt;/code&gt; type that is expected by the frontend and uses the TODO's &lt;code&gt;HTML&lt;/code&gt; method to create the row. Additionally, it uses &lt;code&gt;select&lt;/code&gt; with a &lt;code&gt;default&lt;/code&gt; case instead of simply pushing to the channel so the function will not block if there is no frontend receiving from the channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Touches
&lt;/h2&gt;

&lt;p&gt;With the main structure and functionality in place, only a few minor tweaks are required to finish the seamless integration between &lt;code&gt;babyapi&lt;/code&gt; and HTMX.&lt;/p&gt;

&lt;p&gt;By default, &lt;code&gt;babyapi&lt;/code&gt; is setup to respond to successful &lt;code&gt;DELETE&lt;/code&gt; requests with &lt;code&gt;204 No Content&lt;/code&gt;, but HTMX treats this response as the server requesting to &lt;a href="https://htmx.org/docs/#requests" rel="noopener noreferrer"&gt;disable the swap&lt;/a&gt;. This means our deleted rows will not disappear until the page is refreshed, which is not okay in a modern web application. In order to enable swaps, HTMX needs a &lt;code&gt;200 OK&lt;/code&gt; response. Once again, &lt;code&gt;babyapi&lt;/code&gt; provides the necessary flexibility to achieve this with the &lt;code&gt;api.SetCustomResponseCode&lt;/code&gt; modifier:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetCustomResponseCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MethodDelete&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This simply instructs the default &lt;code&gt;DELETE&lt;/code&gt; handler to respond with &lt;code&gt;http.StatusOK&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;All that's left is adding a little style. I won't claim to be a frontend engineer or a UI designer, so I just used &lt;a href="https://getuikit.com" rel="noopener noreferrer"&gt;UIKit&lt;/a&gt; to easily add modern-looking style to the HTML table and buttons. As mentioned throughout the article, the CSS classes and other small details are excluded since they are not directly relevant to the tutorial. See the full example &lt;a href="https://github.com/calvinmclean/babyapi/blob/main/examples/todo-htmx/main.go" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt; to try running it for yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storage Layer
&lt;/h2&gt;

&lt;p&gt;At this point, we have a fully functioning web app for managing TODOs. If you want to take it one step further and use it for a real TODO tracker instead of a demo, you just need to add persistent storage. &lt;code&gt;babyapi&lt;/code&gt; also simplifies this with the &lt;code&gt;babyapi/storage&lt;/code&gt; package. This package provides a generic implementation of the &lt;code&gt;Storage&lt;/code&gt; interface with helpers for setting up local file or Redis storage. Add the following to the &lt;code&gt;main&lt;/code&gt; function to save the TODOs in a JSON file:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewFileDB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hashmap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
    &lt;span class="n"&gt;Filename&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"todos.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
&lt;span class="p"&gt;})&lt;/span&gt;&lt;br&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
    &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;&lt;a href="https://dev.to/span&amp;gt;&amp;lt;span%20class="&gt;db&lt;/a&gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"TODO"&lt;/span&gt;&lt;span class="p"&gt;&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Conclusion&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;In this tutorial, we extended the super simple &lt;code&gt;babyapi&lt;/code&gt; introduction example to implement an HTMX frontend without adding too much complexity. This shows how &lt;code&gt;babyapi&lt;/code&gt;'s RESTful API and HTMX, a frontend library designed for RESTful backends, are a perfect fit together. It's easier than ever to create a responsive and dynamic web application.&lt;/p&gt;

&lt;p&gt;The steps here also demonstrate the flexibility of &lt;code&gt;babyapi&lt;/code&gt;. Although the default API provided is sufficient for all CRUD functionality, our real-world usecases often introduce more requirements and variance which &lt;code&gt;babyapi&lt;/code&gt; is able to handle gracefully. Hopefully this has revealed how &lt;code&gt;babyapi&lt;/code&gt; can be used to implement your next API-driven application!&lt;/p&gt;

&lt;p&gt;I encourage you to continue experimenting with &lt;code&gt;babyapi&lt;/code&gt; and HTMX to learn more! Here are some ideas to get you started with extending this example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a toggle to show complete, incomplete, or all TODOs&lt;/li&gt;
&lt;li&gt;Allow "un-completing" a TODO&lt;/li&gt;
&lt;li&gt;Add a &lt;code&gt;CreatedAt&lt;/code&gt; field to the &lt;code&gt;TODO&lt;/code&gt; struct and sort by this date (you can implement &lt;code&gt;render.Binder&lt;/code&gt; for &lt;code&gt;TODO&lt;/code&gt; to automatically set &lt;code&gt;CreatedAt&lt;/code&gt; on new items)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>go</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>The Easiest Way to Create a REST API With Go</title>
      <dc:creator>Calvin McLean</dc:creator>
      <pubDate>Sun, 03 Dec 2023 01:42:29 +0000</pubDate>
      <link>https://forem.com/calvinmclean/the-easiest-way-to-create-a-rest-api-with-go-20bo</link>
      <guid>https://forem.com/calvinmclean/the-easiest-way-to-create-a-rest-api-with-go-20bo</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Go is known for being easy to learn and providing the fastest path to production. With HTTP functionality built-in to the standard library, you have everything you need without any external dependencies. The lower-level access to HTTP can be a breath of fresh air if you are accustomed to bloated frameworks, but it results in a lot of boilerplate code which eventually becomes tiring to write over and over. This is where &lt;a href="https://github.com/calvinmclean/babyapi" rel="noopener noreferrer"&gt;&lt;code&gt;babyapi&lt;/code&gt;&lt;/a&gt; comes in. I created this library to enable super simple creation of REST APIs without forcing a specific structure on your whole application. It's so easy that a baby could do it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Baby API
&lt;/h2&gt;

&lt;p&gt;All you need to get started with &lt;code&gt;babyapi&lt;/code&gt; is a struct that implements the &lt;code&gt;babyapi.Resource&lt;/code&gt; interface. This is easily achieved by extending the &lt;code&gt;babyapi.DefaultResource&lt;/code&gt; struct. After completing this simple step, you will have an HTTP API capable of create, read, update, and delete operations. Additionally, &lt;code&gt;babyapi&lt;/code&gt; provides an HTTP client to interact with the API, a CLI to easily use this client, and some shortcuts for unit testing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;babyapi&lt;/code&gt; can also take you beyond the basics. It provides plenty of options for extending the API behavior, adding validation, and even custom API routes. Any storage backend can be integrated by implementing the simple &lt;code&gt;Storage&lt;/code&gt; interface.&lt;/p&gt;

&lt;p&gt;This article just scratches the surface of what &lt;code&gt;babyapi&lt;/code&gt; is capable of. If you are interested in learning more, keep an eye out for my future articles and star the &lt;a href="https://github.com/calvinmclean/babyapi" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;The goal of &lt;code&gt;babyapi&lt;/code&gt; is to be so easy that a baby could do it. As previously mentioned, &lt;code&gt;babyapi.DefaultResource&lt;/code&gt; already implements the required interface, so it can be used as a starting point for simple resource types. Besides simply implementing the interface, this default struct implements some validations around the ID and uses &lt;a href="https://github.com/rs/xid" rel="noopener noreferrer"&gt;&lt;code&gt;rs/xid&lt;/code&gt;&lt;/a&gt; to create a unique identifer on new resources.&lt;/p&gt;

&lt;p&gt;Here is a simple example that extends the default to create an API for TODO items:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"github.com/calvinmclean/babyapi"&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;TODO&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;babyapi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultResource&lt;/span&gt;
    &lt;span class="n"&gt;Title&lt;/span&gt;       &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Description&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Completed&lt;/span&gt;   &lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;babyapi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewAPI&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;
        &lt;span class="s"&gt;"TODOs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/todos"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TODO&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunCLI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, all you need to do is run the server and use the CLI to interact with it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run main.go serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcalvinmclean%2Fbabyapi%2Fmain%2Fexamples%2Fsimple%2Fsimple.gif" class="article-body-image-wrapper"&gt;&lt;img alt="Simple Example" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcalvinmclean%2Fbabyapi%2Fmain%2Fexamples%2Fsimple%2Fsimple.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are too many features to cover in this article, so the best ways to learn more are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/calvinmclean/babyapi#getting-started" rel="noopener noreferrer"&gt;Getting Started tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/calvinmclean/babyapi#examples" rel="noopener noreferrer"&gt;Additional examples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pkg.go.dev/github.com/calvinmclean/babyapi" rel="noopener noreferrer"&gt;&lt;code&gt;pkg.go.dev&lt;/code&gt; documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;My future articles :)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Client
&lt;/h2&gt;

&lt;p&gt;The struct used to create the server also enables a built-in client for interacting with the &lt;code&gt;babyapi&lt;/code&gt; server. The client can also be used in the application or other Go applications interacting with your API. It supports top-level and nested API resources and allows easy access to all CRUD endpoints.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Create a client from an existing API struct (mostly useful for unit testing):&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serverURL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Create a client from the Resource type:&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;babyapi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/todos"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Create a new TODO item&lt;/span&gt;
&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"use babyapi!"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c"&gt;// Get an existing TODO item by ID&lt;/span&gt;
&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetID&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="c"&gt;// Get all incomplete TODO items&lt;/span&gt;
&lt;span class="n"&gt;incompleteTODOs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Values&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"completed"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c"&gt;// Delete a TODO item&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetID&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client provides more general methods for interacting with server: &lt;code&gt;MakeRequest&lt;/code&gt; and &lt;code&gt;MakeRequestWithResponse&lt;/code&gt;. The client is also cutomizable. You can replace the underlying &lt;code&gt;http.Client&lt;/code&gt; to use a custom &lt;code&gt;RoundTripper&lt;/code&gt; or use &lt;code&gt;SetRequestEditor&lt;/code&gt; to add a pre-request function that modifies the &lt;code&gt;http.Request&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storage
&lt;/h2&gt;

&lt;p&gt;You can bring any storage backend to &lt;code&gt;babyapi&lt;/code&gt; by implementing the &lt;code&gt;Storage&lt;/code&gt; interface. By default, the API will use the built-in &lt;code&gt;MapStorage&lt;/code&gt; which just uses an in-memory map.&lt;/p&gt;

&lt;p&gt;In an effort to provide actual persistent storage out of the box, the &lt;code&gt;babyapi/storage&lt;/code&gt; package uses &lt;a href="https://github.com/madflojo/hord" rel="noopener noreferrer"&gt;&lt;code&gt;madflojo/hord&lt;/code&gt;&lt;/a&gt; to support a variety of key-value store backends. Additionally, &lt;code&gt;babyapi/storage&lt;/code&gt; provides helper functions for initializing the &lt;code&gt;hord&lt;/code&gt; client for Redis or file-based storage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewFileDB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hashmap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Filename&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"storage.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRedisDB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"localhost:6379"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"TODO"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion &amp;amp; Roadmap
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;babyapi&lt;/code&gt; is a very new project and still has a lot of potential for new features and improvements. However, my goal is to keep it as simple and barebones as possible, so I plan to focus on usability and testing improvements. I will add new features if use-cases or feature requests come up. These are some of the things coming next:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improve CLI to have better usage instructions and implement flags when multiple IDs are required for nested APIs&lt;/li&gt;
&lt;li&gt;Improve default logging and implement log-levels&lt;/li&gt;
&lt;li&gt;Allow CLI to access non-CRUD endpoints (endpoints added by &lt;code&gt;AddCustomRoute&lt;/code&gt; or &lt;code&gt;AddCustomIDRoute&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Create more automated testing. Ideally, I will create a generator that reads your source file using &lt;code&gt;babyapi&lt;/code&gt; and generates a test structure with some default tests and room to extend them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also keep an eye out for more articles that dig deeper into some of the core features of &lt;code&gt;babyapi&lt;/code&gt;!&lt;/p&gt;

</description>
      <category>go</category>
      <category>showdev</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How I Learned Generics in Go</title>
      <dc:creator>Calvin McLean</dc:creator>
      <pubDate>Sun, 12 Nov 2023 22:52:32 +0000</pubDate>
      <link>https://forem.com/calvinmclean/how-i-learned-generics-in-go-3nc6</link>
      <guid>https://forem.com/calvinmclean/how-i-learned-generics-in-go-3nc6</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Added in Go 1.18, generics were a controversial and long-awaited addition to the language. Go purists feared the change would encourage lazy practices and ruin Go's reputation for favoring simplicity over complexity. More recent Gophers were missing the utility of generics from other languages and eagerly awaited this update. I found myself somewhere in the middle.&lt;/p&gt;

&lt;p&gt;I didn't have any desire to use generics initially, but understood that it could be useful in some cases. After working with Go long enough, I was already accustomed to thinking in a way that didn't involve generics and never thought twice about rewriting a &lt;code&gt;GetKeys(map[string]interface{})&lt;/code&gt; function for the hundredth time. It wasn't until reading the release notes for Go 1.21, which added &lt;a href="https://pkg.go.dev/slices"&gt;&lt;code&gt;slices&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://pkg.go.dev/maps"&gt;&lt;code&gt;maps&lt;/code&gt;&lt;/a&gt; packages to the standard library, that I started thinking more about generics. These packages are all built around generic type parameters and add common operations for the slice and map data structures. &lt;code&gt;maps.Keys&lt;/code&gt; is currently in the &lt;a href="https://pkg.go.dev/golang.org/x/exp/maps#Keys"&gt;experimental version&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Seeing how the standard library was able to use generics to expand its own functionality and alleviate some common pain points really opened my eyes to the potential of this feature. Originally, it seemed like generics were added to placate a few users, but these recent additions made me realize that generics are being taken seriously in Go and are worth a fresh look. I started thinking about how I could leverage generics in my own programs to reduce code duplication and make other improvements.&lt;/p&gt;

&lt;h2&gt;
  
  
  First Attempt: API Layer
&lt;/h2&gt;

&lt;p&gt;When thinking about duplicated code in my &lt;a href="https://github.com/calvinmclean/automated-garden"&gt;&lt;code&gt;automated-garden&lt;/code&gt;&lt;/a&gt; project, the first thing that comes to mind is all of my API handlers. The server side of this application implements a few straightforward CRUD APIs following RESTful principles. Each resource type implements handlers for the different HTTP verbs and mostly interacts with the storage layer. I created a very simple setup for the API handlers following this formula:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Each resource has an ID, which is included in the URL&lt;/li&gt;
&lt;li&gt;Middleware is used to fetch a resource from storage and put it into the request context&lt;/li&gt;
&lt;li&gt;GET endpoints just use &lt;a href="https://github.com/go-chi/render"&gt;&lt;code&gt;go-chi/render&lt;/code&gt;&lt;/a&gt; to create the HTTP response&lt;/li&gt;
&lt;li&gt;Other endpoints, like PATCH and DELETE, read the resource from context and use it to perform additional actions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The only difference between the &lt;code&gt;GET&lt;/code&gt; handlers is the type that is read from context and the response created. This, of course, sounds like a great use-case for generics! However, after thinking about it more and trying out a basic implementation, I realized it is possible to implement this using the &lt;code&gt;render.Renderer&lt;/code&gt; interface that my types already implement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getGardenFromContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Renderer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gardenCtxKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;GardenResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getter&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Renderer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;getter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrRender&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;chi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mux&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;With&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gardenContextMiddleware&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getGardenFromContext&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to implement this, I had to change the middleware to store the &lt;code&gt;GardenResponse&lt;/code&gt; in the context instead of the raw &lt;code&gt;Garden&lt;/code&gt; struct. The &lt;code&gt;getGardenFromContext&lt;/code&gt; now has to return a &lt;code&gt;render.Renderer&lt;/code&gt; so it can satisfy the input to &lt;code&gt;get&lt;/code&gt; without additional type assertions. This makes the function less usable in other scenarios. One solution is replacing the &lt;code&gt;getter&lt;/code&gt; input with just the context key, so the handler can use a more generalized &lt;code&gt;getRendererFromContext&lt;/code&gt; function. Another option is to use a generic type parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getGardenFromContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;GardenResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// function body unchanged&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Renderer&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;getter&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// function body unchanged&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;chi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mux&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;With&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gardenContextMiddleware&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;GardenResponse&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;getGardenFromContext&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The introduction of the generic type parameter adds a bit of clarity about the types being used and actually reduces the abstraction when compared to the interface-only approach. This is going to be appreciated by those concerned about unnecessary increases to complexity. Additionally, the &lt;code&gt;render.Renderer&lt;/code&gt; interface is still used as a constraint for our generic type, so all of the benefits are still available. Finally, the &lt;code&gt;getGardenFromContext&lt;/code&gt; function is returned to its original type so it can still be used outside of this response rendering scenario. In the end, it just comes down to personal preference.&lt;/p&gt;

&lt;p&gt;The hidden truth for both implementations is that everything that isn't generalized is moved into the specific middlewares or the &lt;code&gt;Render&lt;/code&gt; methods of each type. This is already how my application was designed, so it made the refactor pretty easy. However, this approach is not going to work as well for the more complicated handlers like &lt;code&gt;POST&lt;/code&gt; and &lt;code&gt;PATCH&lt;/code&gt; which do more than just returning the resource and have drastically different behaviors for each resource.&lt;/p&gt;

&lt;p&gt;Ultimately, I decided to scrap this generic API idea, at least for now. While it slightly reduced code duplication, it doesn't work in all cases and furthers the separation between the application code and HTTP layer, which I see as an unnecessary abstraction. However, if I took the time to implement it as a generic API framework and rebuild my &lt;code&gt;automated-garden&lt;/code&gt; API around it, that might be a different story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Attempt: Storage Layer
&lt;/h2&gt;

&lt;p&gt;Determined to implement generics and delete some lines of code, I turned my sights to the storage layer of my application. I designed my storage around key-value pairs since I started by storing resources in YAML files. This eventually evolved to use &lt;a href="https://github.com/madflojo/hord"&gt;&lt;code&gt;madflojo/hord&lt;/code&gt;&lt;/a&gt; to interact with key-value data stores like Redis. The function to read a &lt;code&gt;*pkg.Garden&lt;/code&gt; from storage looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;getGarden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Garden&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;dataBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error getting Garden: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Garden&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error parsing Garden data: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This uses the &lt;code&gt;hord&lt;/code&gt; client's &lt;code&gt;Get(key)&lt;/code&gt; which returns a JSON &lt;code&gt;[]byte&lt;/code&gt;. This is unmarshalled into the &lt;code&gt;pkg.Garden&lt;/code&gt; struct. As you can probably imagine, the getters for other types are the same except the &lt;code&gt;var result pkg.Garden&lt;/code&gt; line specifies a different type. Another potential usecase for generics! Or, once again, maybe it can be abstracted with interfaces.&lt;/p&gt;

&lt;p&gt;In this case, the &lt;code&gt;var result pkg.Garden&lt;/code&gt; line can be relocated outside of the function and passed in as a destination pointer. Instead of returning a &lt;code&gt;*pkg.Garden&lt;/code&gt;, &lt;code&gt;get&lt;/code&gt; will pass the destination pointer to &lt;code&gt;json.Unmarshal&lt;/code&gt;. The function can now be used more generally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetGarden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;xid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Garden&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Garden&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gardenPrefix&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;dataBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error getting data: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error parsing data: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is basically a wrapper around &lt;code&gt;json.Unmarshal&lt;/code&gt; and using it will be familiar for any Go engineer. The same thing will work for &lt;code&gt;save&lt;/code&gt; since it is similar to &lt;code&gt;json.Marshal&lt;/code&gt;. Then, the only other function that touches the storage library is &lt;code&gt;getMultipleGardens&lt;/code&gt; (and same for other types). Since I am using a simple key-value store instead of SQL tables, I have to get a list of keys and then fetch each one, which looks like this if I convert to use &lt;code&gt;interface{}&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetGardens&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Garden&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Garden&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getMultiple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gardenPrefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;getMultiple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error getting keys: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gardenPrefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error getting Garden: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This results in a compiler error: &lt;code&gt;cannot use result (variable of type []*pkg.Garden) as []interface{} value in argument to c.getMultiple&lt;/code&gt;. This error occurs because &lt;code&gt;[]interface{}&lt;/code&gt; does not behave in the same way as &lt;code&gt;interface{}&lt;/code&gt;. Since it is a slice, each individual element needs to be typed separately. Even if this was valid, it would not work because the &lt;code&gt;c.get&lt;/code&gt; call is using an emtpy &lt;code&gt;var result interface{}&lt;/code&gt; and never knows which type to unmarshal into.&lt;/p&gt;

&lt;p&gt;Now, this should be a case where generics can help since it is an abstraction on a data structure. Here is how it looks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getOne&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error missing database connection"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;dataBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error getting data: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error parsing data: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getMultiple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error getting keys: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;getOne&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error getting data: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using generics here even simplifies the calling code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetGarden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;xid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Garden&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;getOne&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Garden&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gardenKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetGardens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getEndDated&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Garden&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;getMultiple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Garden&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;getEndDated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gardenPrefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is a generic implementation that leaves the external interface of the storage client unchanged, but significantly reduces duplication internally. Take a look at the &lt;a href="https://github.com/calvinmclean/automated-garden/pull/140"&gt;full PR here&lt;/a&gt;. This is an ideal use of generics because it takes advantage of code reuse, but doesn’t expose the added complexity and abstraction to consumers of the package. Additionally, it was a very straightforward refactor because it strictly follows the advice of using generics &lt;a href="https://go.dev/blog/when-generics"&gt;"where the only difference between the copies is that the code uses different types."&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Another Use-Case: Request Retries
&lt;/h2&gt;

&lt;p&gt;This final example is the result of solving a problem that occurred organically rather than an attempt to brute-force generics into my existing application. While building a tool to &lt;a href="https://dev.to/calvinmclean/manage-dev-articles-with-git-and-github-actions-13md"&gt;publish markdown files to DEV as articles using GitHub Actions&lt;/a&gt;, I ran into rate limiting from the DEV API. Normally, HTTP client retries are done using a custom &lt;code&gt;http.Transport&lt;/code&gt;, but this program uses a client library that doesn't allow setting the &lt;code&gt;Transport&lt;/code&gt;. Instead, I turned to creating a wrapper function using generic type parameters for the response struct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;doWithRetry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;numRetries&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;initialWait&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;numRetries&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nb"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusTooManyRequests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initialWait&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nb"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"exhausted retry limit %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;numRetries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;getPublishedArticles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;doWithRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetUserPublishedArticlesResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetUserPublishedArticlesWithResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error getting articles: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might notice that I don't include any type parameters when using the &lt;code&gt;doWithRetry&lt;/code&gt; function. This is a nice feature of generics in Go that allows the type parameter to be inferred when it is used in multiple places. In this case, the return type of the &lt;code&gt;do&lt;/code&gt; function has to be defined, so there is no need to duplicate this in the type parameter.&lt;/p&gt;

&lt;p&gt;Another interesting aspect of this this example is that it takes advantage of Go's &lt;a href="https://dev.to/calvinmclean/the-magic-of-interfaces-in-go-5gga"&gt;implicit interfaces&lt;/a&gt; to define an interface outside of the client library the defines all the structs and &lt;code&gt;StatusCode()&lt;/code&gt; methods. The generic type parameter requires types that implement the interface so it can check response codes without additional type assertions. Using generics does not create an "either-or" scenario with interfaces, and often when using generics, interfaces are extremely useful to introduce constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I chose to learn about generics in Go by finding how the feature can improve my &lt;a href="https://github.com/calvinmclean/automated-garden"&gt;&lt;code&gt;automated-garden&lt;/code&gt;&lt;/a&gt; project. In this process, I learned how to use generics and more importantly, &lt;em&gt;when&lt;/em&gt; to use generics. I was reminded of the pitfalls that come from overusing abstraction and why I originally appreciated the language discouraging these practices, but I also see a lot of potential for this new feature.&lt;/p&gt;

&lt;p&gt;Generic type parameters enable the simplification of small, tedious repetition and open the door to whole new levels of abstraction. In some cases, the feature provides a second way to achieve the same things that were already possible. In other cases, it unlocks whole new options that were previously unachievable. Most of the time, this level of abstraction is unnecessary, but it also allows for new and creative solutions. Although the potential to create overly-complex and abstracted code is increased, it is important for a programming language to provide these necessary tools. Ultimately, it is up to the programmer to decide how and when to use them. This reminds me of the famous quote: &lt;a href="https://en.wiktionary.org/wiki/if_all_you_have_is_a_hammer,_everything_looks_like_a_nail"&gt;"If all you have is a hammer, everything looks like a nail."&lt;/a&gt; Luckily, with the addition of more tools to the language, we have much more than a hammer at our disposal.&lt;/p&gt;

</description>
      <category>go</category>
      <category>programming</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
