My Journey with Fuego
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 Fuego.
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.
First Impressions
The first thing I noticed was the brevity of Fuego’s “Hello World” setup. I got a basic server running in minutes:
package main
import "github.com/go-fuego/fuego"
func main() {
s := fuego.NewServer()
fuego.Get(s, "/", func(c fuego.ContextNoBody) (string, error) {
return "Hello, World!", nil
})
s.Run()
}
I was pleasantly surprised by how familiar it felt—almost like Gin
but with OpenAPI out of the box.
A More Realistic Use Case
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.
Here’s a simplified version of one of my routes:
type UserInput struct {
Name string `json:"name" validate:"required"`
}
type UserOutput struct {
Message string `json:"message"`
}
func main() {
s := fuego.NewServer()
fuego.Post(s, "/user", handleUser)
s.Run()
}
func handleUser(c fuego.ContextWithBody[UserInput]) (UserOutput, error) {
in, err := c.Body()
if err != nil {
return UserOutput{}, err
}
return UserOutput{
Message: "Hello, " + in.Name,
}, nil
}
Here’s what stood out to me:
-
Typed Handlers: By specifying
fuego.ContextWithBody[UserInput]
, Fuego automatically deserializes JSON into myUserInput
struct. -
Validation: The
validate:"required"
tag ensuresName
is present. If it’s missing, Fuego handles the error gracefully without me writing extra code. -
Response: Returning a
UserOutput
struct automatically gets serialized back to JSON.
This saved me from a lot of boilerplate—no json.Unmarshal
, no separate validation library integration, and no custom error marshalling.
Why Fuego Felt Different
1. Native-Like Feel
While frameworks like Gin wrap net/http
in a particular style, Fuego felt surprisingly native. Under the hood, it’s still just net/http
(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.
2. Automatic OpenAPI Generation
I used to maintain a separate .yaml
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.
3. Validation and Error Handling
The built-in validation (based on go-playground/validator
) was straightforward, and custom error handling was simpler than what I was used to. If the UserInput
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.
A Peek at Transformations
In one particular scenario, I needed to ensure all incoming Name
fields were lowercase. Instead of writing the same “convert to lowercase” logic in every handler, I used Fuego’s InTransform
method on my input struct:
func (r *UserInput) InTransform(ctx context.Context) error {
r.Name = strings.ToLower(r.Name)
if r.Name == "" {
return errors.New("Name cannot be empty")
}
return nil
}
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.
Roadblocks or Downsides?
I did hit a couple of snags, which I think are worth mentioning:
Newer Ecosystem
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.Less Built-In Middleware
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, standardnet/http
compatibility means I could bring in external libraries or reuse existing middleware logic.
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.
Final Takeaways
Fuego 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.
- Real Productivity: My code was easier to read, and I spent less time on boilerplate.
- Documentation On Autopilot: The automatic OpenAPI generation kept my team in sync with any API changes.
-
Easy Transitions: Because I could still use
net/http
under the hood, migrating existing handlers was relatively painless.
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—I highly recommend giving Fuego a shot. It genuinely saved me time and made my development process smoother, all while staying true to Go’s “less is more” ethos.
For those curious, the GitHub repository has plenty of detailed explanations and a 2025 Roadmap 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.
Top comments (0)