why i stopped using go frameworks

When starting a new Go project, the reflex is often go get github.com/gin-gonic/gin. It’s easy, it’s fast, and it feels like the right choice. But for my latest project an Equipment Rental API I disagreed.

I decided to go "vanilla". With the updates in Go 1.22+, the standard library's net/http router is finally powerful enough to handle complex routing without the overhead of a third-party framework. Here is how I built a production-ready system with zero dependency bloat.

the clean approach

Spaghetti code kills projects. To prevent this, I enforced clean architecture. The rule is simple: dependencies point inwards. The database doesn't know about the HTTP handler, and the HTTP handler doesn't know about the SQL query. This pattern allows me to swap the database or the web server without rewriting the business logic.

Gone are the days of manual method checking. The standard http.ServeMux now understands methods and wildcards natively. Look how clean this is:

func (s *Server) RegisterRoutes() {
    // Native method matching! No more "if r.Method == POST"
    s.mux.HandleFunc("POST /api/v1/equipment", s.handleCreateEquipment)
    s.mux.HandleFunc("GET /api/v1/equipment/{id}", s.handleGetEquipment)
}
 

Since I didn't use a framework, I built a simple middleware chain to handle cross-cutting concerns like logging, CORS, and panic recovery. It's just a basic onion architecture pattern chaining http.Handler functions.

The hardest part wasn't the HTTP layer; it was the reservation state machine. A reservation can't jump from PENDING to COMPLETED without being APPROVED. To solve this, I implemented the logic strictly in the service layer. Instead of a controller checking if status == "PENDING", the domain entity has methods like reservation.Approve(), which returns an error if the transition is illegal. This keeps the logic encapsulated.

final thoughts

After finishing the project, the benefits of avoiding frameworks became clear. There is no reflection overhead from binding engines, giving better performance. When Go 1.25 comes out, I don't have to wait for a framework author to update their package. And from a hiring perspective, any Go engineer understands net/http, whereas not everyone knows specific frameworks like Fiber or Echo.

Building without frameworks forces you to understand HTTP. You learn how context works, how to handle JSON encoding manually, and how to structure code properly. If you are learning backend engineering, skip the framework. Do it the hard way once, and you'll be a better engineer forever.

want to see the full implementation?

I open-sourced the entire project, including the Docker setup and CI/CD pipeline.

want to see how it works?view on github