I'm not a big fan of Echo using nonstandard context types and nonstandard function signatures for its handlers/middleware. It boxes you into making "Echo libraries" instead of "HTTP libraries". I would recommend chi for those who want something that plays nicely with the standard library: https://github.com/go-chi/chi
I used to be of the same opinion but after writing a lot of Go web services I don’t agree anymore. Go’s HTTP interface is a bit weird and while I am sure there is a reason for all the strange choices, for average non-performance critical code I just want interfaces that are easy to use. For example: only being able to ready to body once, not being able to get the status code that was set, …
Deviating from the standard allows you to tackle these ‘problems’. In the end I think there is room for multiple frameworks/libs for different styles and purposes.
I think this is a perfectly reasonable opinion. Nothing wrong with adding abstractions if you find them useful. I just prefer to combine small stdlib-friendly libraries so I stick with that. Echo (and Gin) are a more 'batteries-included' approach, and their popularity speaks to a demand for that approach.
It's not difficult to make a stdlib-compatible handler with bells and whistles: just stuff everything into a context and provide helper functions for retrieving those variables from the context. This is what https://pkg.go.dev/github.com/go-chi/chi#URLParam does.
Echo and Gin deviate from http.Handler because they want to be frameworks and stamp their framework-specific type signatures all over your codebase.
> just stuff everything into a context and provide helper functions for retrieving those variables from the context
That doesn't feel in spirit with the use of context. I am not saying you are wrong just that it makes me realize I would not have even considered doing something like this.
Have you worked with a library that does this? Curious if this is this more common than I realize?
Echo and Gin have their own Context structs, except instead of http.Request wrapping their Context struct, their Context struct wraps http.Request (thereby rendering it incompatible with the stdlib). It's not a far stretch to have these frameworks retrieve their Context from the http.Request. Instead of
// echo Handler
func(c *echo.Context) {
}
they could do
// standard Go handler
func(w http.ResponseWriter, r *http.Request) {
c := echo.GetContext(r)
}
I like that it provides a useful set of methods over raw stdlib types. If you need to integrate other libs, it's quite easy to wrap them and call ServeHTTP(w, r)
Isn't it the same as *gin.context in go gin ? From what I see (not an experienced Go programmer), in gin you get the request through gin.context and in Chi you get the context through the request ?
The context package didn't exist when Gin was created, so they invented their own solution. There was a proliferation of these context types until the standard library created a common solution. chi uses it, but Echo doesn't. The biggest advantage is that the stdlib context key type is interface{}, so you can use unexported types to create private key namespaces. It's also immutable and thread-safe. Gin and Echo use plain strings that are easy to clobber. This blog post discusses how the stdlib context works: https://go.dev/blog/context (see userip bit about the unexported types).
Interesting. I find it difficult to compare the plethora of web frameworks in Go. But coming from Python, I'm also familiar to this kind of situation...
I can see how having Echo's build-ins be a benefit if you have a pretty big applications with rich API and quite a few people working on it. But in case for microservices - it's just makes things needlessly complicated.
I've used Echo on a few occasions and it is nice little Go web framework.. or more a library.. it doesn't bringt much extra fuzz.
The reason why I chose Echo among all of the Go web "frameworks" was that it didn't use FastHTTP, which is used by many (most?). I however ran into a (simple) pattern of endpoints a long time ago that you could not be matched by FastHTTP and realized the speed improvements may not be worth it. There was also no http2 support back then. Too many people just look at micro-benchmarks for these frameworks.
Their rendering handling seems to be a lot better than gofiber's[1] which downloads nearly 1,000 packages and sadly that ruled it out as a viable framework when I was looking for web frameworks to use.
A package appearing in go.sum does not necessarily mean it's being used. It just means it has been downloaded once, but might have been replaced by some other version at some point.
This has bitten me before. A security scanner reported a vulnerability from this that was blocking me from my “use X at work” request.
I ended up having to teach the vendor of the tool about go mod.
But if you are publishing a “release” I personally think it is good to tidy that up. Even just to prevent misconceptions. No reason to preserve ancient versions and especially failed library experiments.
I used Echo for one of my projects few years ago. I don’t remember which version it was, but authors (Labstack) were releasing major versions like every year or so, after two migrations I found myself in need to migrate again. That project is in maintenance mode, so still uses old Echo, but if I ever need to migrate, that would be to Fiber.
I just started using echo for a personal project - just a boring old server rendered webapp. Overall, it has some niceties which can save a lot of time. The documentation and cookbook cover the most common usage patterns. I like that it doesn't enforce too many conventions unlike Rails/Django.
P.S: Logging is still a bit of mess in the Go ecosystem!
Didn't the Go team say that there's a logging interface and that's where they are going to leave it. If you want more robust logging, you can add it. log.New() is for that purpose. If you want a struct-log, you'll have to define one for your needs. Logging is very environment specific. What I log to the console locally is different from what I log to, say, AWS CloudWatch in production.
I think the stance on logging by the core Go team is the right way to go about it. Just provide a basic logger, an interface, and let the projects determine how they want to route those logs and what kind of data should it include.
The Go community steers towards 'one way of doing things', meaning that packages that don't reuse established interfaces tend to be less popular compared to the ones that do (gorilla, chi, or just std).
I would argue that 'one way of doing things' is really at a much lower level, not at the package or module level. That was tied to "one way to loop, one way to place curly braces" Extending this idea to modules is not actually in line with how the GO team and community works.
Ah, I wasn't aware! It was heavily used a few years ago.
> Extending this idea to modules is not actually in line with how the ... community works
This is not my experience (with the community).
Packages that heavily reuse common interfaces (combinations of io.Reader|Writer|Closer|Seeker, http.Handler, http.ResponseWriter etc) tend to be more popular and recommended than packages that invent their own interfaces.
That is, unless the recommendation is just 'stick to the std', which has been a knee-jerk reaction from the community (reddit, slack) for a lot of problems, especially concerning new comers.
Gin and Echo cover similar feature scopes (the HTTP plumbing around your business logic, that you write in handler functions), but I chose Echo for my project[1] because:
- it ships a pretty extensive collection of middlewares that will get you to 80% faster
- the documentation[2] is really well written and accessible to new web developers: most "must have" features have a cookbook page, so you don't have to google for blog posts.
- it does indeed use a custom handler function signature, but in my opinion this one is better (returning errors instead of handling them locally in each handler). You can still use handlers and middlewares written for other frameworks (for example the great statigz[3] handler) through the `echo.WrapHandler` and `echo.WrapMiddleware` functions
I have used echo in the past to build control plane services for a famous logging company. Its great to get things up and running closer to production quality. My team was easily able to extend this framework and add auditing and integrations with other internal tools within the company.
The one thing, authors could build as a differentiating factor is to provide openapi spec support out of the box. I have not seen many golang web frameworks provide first class support for building using openapi spec.
I worked very briefly with echo and was left seriously unimpressed. I was called to fix a bug where some url paths were not processed properly. It turned out that echo does not use the standard Go net/url functions for url decoding, but uses some esoteric, roll-your-own, non-standard-compliant implementation instead. Cutting corners around web standards is ill-conceived minimalism, suitable only for hobby projects.
Please read the underlying benchmark code before evaluating this web framework as a "High performance" option.
People are welcome to create whatever abstractions they want but I highly encourage new golang users to experiment without web frameworks using the standard library and minimalist libraries like gorilla/mux, sqlx, and squirrel, to compose your web applications with much less of a headache.