<?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>DEV Community: Mike</title>
    <description>The latest articles on DEV Community by Mike (@tizzard).</description>
    <link>https://dev.to/tizzard</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%2F2681533%2Fcac3f14d-79dc-4714-a7cc-0535790946d8.png</url>
      <title>DEV Community: Mike</title>
      <link>https://dev.to/tizzard</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tizzard"/>
    <language>en</language>
    <item>
      <title>How I write Go APIs in 2025 - my experience with Fuego</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Thu, 09 Jan 2025 16:17:29 +0000</pubDate>
      <link>https://dev.to/tizzard/how-i-write-go-apis-in-2025-my-experience-with-fuego-1j5o</link>
      <guid>https://dev.to/tizzard/how-i-write-go-apis-in-2025-my-experience-with-fuego-1j5o</guid>
      <description>&lt;p&gt;&lt;strong&gt;My Journey with Fuego&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;I’ve been a Go developer for a few years now and, like many Gophers, I’ve tried my share of web frameworks. I started off with the standard library, dabbled with Gin, and even spent some time in Fiber. While they’re all great in their own ways, I kept running into scenarios where I either needed more structure or was wasting time juggling multiple libraries for validation, serialization, and documentation. That’s when I came across &lt;strong&gt;&lt;a href="https://github.com/go-fuego/fuego" rel="noopener noreferrer"&gt;Fuego&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;My initial impression was, “Oh, another Go framework,” but after reading about how it leverages modern Go features—especially generics—to automatically generate OpenAPI from actual code, I decided to give it a try for a small internal project. Below is a realistic account of how that went.&lt;/p&gt;




&lt;h2&gt;
  
  
  First Impressions
&lt;/h2&gt;

&lt;p&gt;The first thing I noticed was the brevity of Fuego’s “Hello World” setup. I got a basic server running in minutes:&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/go-fuego/fuego"&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;s&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fuego&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;fuego&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;s&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="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="n"&gt;fuego&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContextNoBody&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="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Hello, World!"&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="n"&gt;s&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;I was pleasantly surprised by how familiar it felt—almost like &lt;code&gt;Gin&lt;/code&gt; but with OpenAPI out of the box. &lt;/p&gt;




&lt;h2&gt;
  
  
  A More Realistic Use Case
&lt;/h2&gt;

&lt;p&gt;Of course, any “Hello World” example doesn’t truly capture a real-world scenario. In my actual application, I needed to accept JSON data, validate it, and return typed responses. With other frameworks, I’d typically write my own JSON decoding and error handling, or rely on custom middlewares that I had to piece together. Fuego did most of that for me through typed route handlers.&lt;/p&gt;

&lt;p&gt;Here’s a simplified version of one of my routes:&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;UserInput&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;Name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"name" validate:"required"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;UserOutput&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;Message&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"message"`&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;s&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fuego&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;fuego&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;s&lt;/span&gt;&lt;span class="p"&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;handleUser&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;Run&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;handleUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;fuego&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContextWithBody&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;UserInput&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UserOutput&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;in&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;Body&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;UserOutput&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;UserOutput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Hello, "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;in&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="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;Here’s what stood out to me:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Typed Handlers&lt;/strong&gt;: By specifying &lt;code&gt;fuego.ContextWithBody[UserInput]&lt;/code&gt;, Fuego automatically deserializes JSON into my &lt;code&gt;UserInput&lt;/code&gt; struct.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation&lt;/strong&gt;: The &lt;code&gt;validate:"required"&lt;/code&gt; tag ensures &lt;code&gt;Name&lt;/code&gt; is present. If it’s missing, Fuego handles the error gracefully without me writing extra code.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response&lt;/strong&gt;: Returning a &lt;code&gt;UserOutput&lt;/code&gt; struct automatically gets serialized back to JSON.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This saved me from a lot of boilerplate—no &lt;code&gt;json.Unmarshal&lt;/code&gt;, no separate validation library integration, and no custom error marshalling.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Fuego Felt Different
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Native-Like Feel&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;While frameworks like Gin wrap &lt;code&gt;net/http&lt;/code&gt; in a particular style, Fuego felt surprisingly native. Under the hood, it’s still just &lt;code&gt;net/http&lt;/code&gt; (and you can bring in your own standard middleware or handlers). In fact, I reused some authentication middleware I’d written for the standard library without skipping a beat.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Automatic OpenAPI Generation&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;I used to maintain a separate &lt;code&gt;.yaml&lt;/code&gt; file for documentation or rely on comments to generate an OpenAPI spec. That approach always felt tedious and prone to drift if I forgot to update the docs. With Fuego, the framework scans the types in my route handlers and creates an OpenAPI spec automatically. Suddenly, my docs were always up to date. It was a relief not having to cross-check endpoints with a separate doc.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Validation and Error Handling&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The built-in validation (based on &lt;code&gt;go-playground/validator&lt;/code&gt;) was straightforward, and custom error handling was simpler than what I was used to. If the &lt;code&gt;UserInput&lt;/code&gt; struct was invalid, Fuego responded with a structured error message, following standardized RFC guidelines. It’s a small detail, but it saved me time and provided a consistent approach across my routes.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Peek at Transformations
&lt;/h2&gt;

&lt;p&gt;In one particular scenario, I needed to ensure all incoming &lt;code&gt;Name&lt;/code&gt; fields were lowercase. Instead of writing the same “convert to lowercase” logic in every handler, I used Fuego’s &lt;strong&gt;&lt;code&gt;InTransform&lt;/code&gt;&lt;/strong&gt; method on my input 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;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;UserInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;InTransform&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="kt"&gt;error&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;Name&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;ToLower&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;Name&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;Name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&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;errors&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;"Name cannot be empty"&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 gets called automatically when the framework processes the request, so by the time my route’s handler sees the data, it’s already transformed and validated.&lt;/p&gt;




&lt;h2&gt;
  
  
  Roadblocks or Downsides?
&lt;/h2&gt;

&lt;p&gt;I did hit a couple of snags, which I think are worth mentioning:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Newer Ecosystem&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Fuego doesn’t have the same large user base as Gin or Echo, so I occasionally had trouble finding blog posts or community examples. However, the repository’s own examples folder was quite rich, and the built-in docs seemed thorough enough for my needs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Less Built-In Middleware&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Some older frameworks come with a full arsenal of pre-built middlewares (like advanced logging, recovery, or security). Fuego ships with a few but not an entire kitchen sink. Thankfully, standard &lt;code&gt;net/http&lt;/code&gt; compatibility means I could bring in external libraries or reuse existing middleware logic.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These issues weren’t deal-breakers for me, given the benefits I was gaining. Over time, I expect Fuego’s community to grow, and I know from experience that minimalism can often be an advantage in the long run.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Takeaways
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Fuego&lt;/strong&gt; managed to strike a sweet spot for me: it gave me enough abstraction to build APIs quickly (with validation, serialization, and doc generation out of the box) without boxing me into a completely different paradigm than the Go standard library. Returning typed structs and letting Fuego handle the rest was a breath of fresh air.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real Productivity&lt;/strong&gt;: My code was easier to read, and I spent less time on boilerplate.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation On Autopilot&lt;/strong&gt;: The automatic OpenAPI generation kept my team in sync with any API changes.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy Transitions&lt;/strong&gt;: Because I could still use &lt;code&gt;net/http&lt;/code&gt; under the hood, migrating existing handlers was relatively painless.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re a Go developer looking for a modern framework that balances convenience with flexibility—and especially if you’re tired of manually maintaining OpenAPI docs—&lt;strong&gt;I highly recommend giving Fuego a shot&lt;/strong&gt;. It genuinely saved me time and made my development process smoother, all while staying true to Go’s “less is more” ethos. &lt;/p&gt;

&lt;p&gt;For those curious, the &lt;a href="https://github.com/go-fuego/fuego" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; has plenty of detailed explanations and a &lt;a href="https://github.com/go-fuego/fuego/discussions/263" rel="noopener noreferrer"&gt;2025 Roadmap&lt;/a&gt; that’s shaping up nicely. I’m excited to see how the project evolves—and I’ll definitely be keeping Fuego in my rotation for future Go services.&lt;/p&gt;

</description>
      <category>go</category>
      <category>api</category>
      <category>openapi</category>
    </item>
  </channel>
</rss>
