Add gorm/audited middleware for Echo-framework in GO
Because I am coming from a solid eco-system (Java
and Spring
), I am trying to find the same exact experience in the youngest Golang
eco-system.
One of the most beautiful parts of spring project is the integration between its parts, every part of the system is well glued with each other.
for example, I am trying here to capture user information from the JWT
token and save it as createdBy
and modifiedBy
with each create/update DB operation.
In Spring, all you will need to do is to implement some spring-data
interface and just provide the current user from the spring-security
context.
But what about Golang
?
I really like the simplicity of the golang libraries/frameworks, but simplicity comes at a cost, the integration points between the libraries is not so solid.
What I want to achieve is to have current user picked from the security context and saved into the database with each entity create/update request.
I find gorm is doing a very good job along with its plugins, especially audited plugin that allows you to easily set the current user in the gorm Scope and that’s it. but it is disconnected from the web layer.
At the web layer, I like echo framework, it is a well-documented library, (actually, I find most the web go frameworks looks very similar, I hope they might be merged one day to concentrate efforts).
echo
(and most of the other go libraries) had the idea of middleware, the middleware allows you to modify/customize/inspect the request before reaching the handler (a.k.a. controller).
So I’ve created a very simple middleware that extracts JWT token from the echo.context
(put by JWT middleware) and then modifies* gorm.DB
instance to set the current user with the user found in JWT token.
The middleware is simple, here’s the code:
func GormJWTInjector(db *gorm.DB) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if userToken := c.Get("user"); userToken != nil {
claims := userToken.(*jwt.Token).Claims.(jwt.MapClaims)
user := User{
gorm.Model{
ID: uint(claims["id"].(float64)),
},
}
db = db.Set("audited:current_user", user)
c.Set(gromDB, db)
}
return next(c)
}
}
}
And here I do register the middleware:
e := echo.New()
db := initDB() // return ref to *gorm.DB
defer db.Close()
e.Use(middleware.JWT([]byte("secret")))
e.Use(GormJWTInjector(db))
And here’s the controller handler function code:
e.POST("/create-product", func(c echo.Context) error {
db := c.Get(gromDB).(*gorm.DB)
db.Create(&Product{code: "my-prod-code"})
return c.String(http.StatusOK, "Hello, World!")
})
Full source code found on Github
That’s all folks.
*)I am sure gorm is not about modifying the global reference, but about copy-on-write
on each request so that the global *gorm.DB
reference looks like as it is shared.