Reputation: 9396
I'm using Gin, https://gin-gonic.github.io/gin/, to build a simple RESTful JSON API with Golang.
The routes are setup with something like this:
func testRouteHandler(c *gin.Context) {
// do smth
}
func main() {
router := gin.Default()
router.GET("/test", testRouteHandler)
router.Run(":8080")
}
My question is how can I pass down an argument to the testRouteHandler function? For example a common database connection could be something that one wants to reuse among routes.
Is the best way to have this in a global variable? Or is there some way in Go to pass along an extra variable to the testRouteHandler function? Are there optional arguments for functions in Go?
PS. I'm just getting started in learning Go, so could be something obvious that I'm missing :)
Upvotes: 49
Views: 53559
Reputation: 1
https://github.com/lennon-guan/gin-utils/blob/main/inject/examples/main.go
I just push my library to github. In this "inject" library, you can easily inject any value into your handler func.
The comment is written in Chinese. Below is the example with English comment:
package main
import (
"fmt"
"sync/atomic"
"github.com/gin-gonic/gin"
"github.com/lennon-guan/gin-utils/inject"
)
func main() {
var counter atomic.Int64
// Add int64 injector
inject.AddInjector(func() int64 {
return counter.Add(1)
})
// Add SomeRes injector
// SomeRes implements io.Closer, so when HandlerFunc returns, SomeRes's Close method will be execute automaticlly
inject.AddInjector(func() SomeResource {
fmt.Println("made a new SomeResource!")
return SomeResource{}
})
engine := gin.Default()
// inject.Wrap2 makes a HandlerFunc with two injected arguments
engine.GET("/", inject.Wrap2(func(c *gin.Context, counter int64, res SomeResource) {
c.String(200, "req %d", counter)
}))
engine.Run(":28080")
}
type SomeResource struct{}
func (SomeResource) Close() error {
fmt.Println("SomeResource as io.Closer released!")
return nil
}
Upvotes: -1
Reputation: 213
Late to the party, so far here is my proposal. Encapsulate methods into the object with private/public vars in it:
package main
import (
"log"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/mattn/go-sqlite3"
)
type HandlerA struct {
Db gorm.DB
}
func (this *HandlerA) Get(c *gin.Context) {
log.Info("[%#f]", this.Db)
// do your thing here...
}
func main() {
r := gin.New()
// Init, should be separate, but it's ok for this sample:
db, err := gorm.Open("sqlite3", "./example.db")
if err != nil {
log.Fatal(err)
}
Obj := new(HandlerA)
Obj.Db = db // Or init inside Object
r := gin.New()
Group := r.Group("api/v1/")
{
Group.GET("/storage", Obj.Get)
}
r.Run(":8080")
}
Upvotes: 9
Reputation: 45140
Handler closures are a good option, but that works best when the argument is used in that handler alone.
If you have route groups, or long handler chains, where the same argument is needed in multiple places, you should set values into the Gin context.
You can use function literals, or named functions that return gin.HandlerFunc
to do that in a clean way.
Example injecting configs into a router group:
Middleware package:
func Configs(conf APIV1Config) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("configKey", conf) // key could be an unexported struct to ensure uniqueness
}
}
Router:
conf := APIV1Config{/* some api configs */}
// makes conf available to all routes in this group
g := r.Group("/api/v1", middleware.Configs(conf))
{
// ... routes that all need API V1 configs
}
This is also easily unit-testable. Assuming that you test the single handlers, you can set the necessary values into the mock context:
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Set("configKey", /* mock configs */)
apiV1FooHandler(c)
Now in the case of application-scoped dependencies (db connections, remote clients, ...), I agree that setting these directly into the Gin context is a poor solution.
What you should do then, is to inject providers into the Gin context, using the pattern outlined above:
Middleware package:
// provider could be an interface for easy mocking
func DBProvider(provider database.Provider) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("providerKey", provider)
}
}
Router:
dbProvider := /* init provider with db connection */
r.Use(DBProvider(dbProvider)) // global middleware
// or
g := r.Group("/users", DBProvider(dbProvider)) // users group only
Handler (you can greatly reduce the boilerplate code by putting these context getters in some helper function):
// helper function
func GetDB(c *gin.Context) *sql.DB {
provider := c.MustGet("providerKey").(database.Provider)
return provider.GetConn()
}
func createUserHandler(c *gin.Context) {
db := GetDB(c) // same in all other handlers
// ...
}
Upvotes: 3
Reputation: 583
Let me try to explain in detail so that you won't get confused.
/books
and your controller is BooksController
BooksController
will try to fetch the books from the database and returns a response.Now, you want this a handler within your BooksController
so that you can access database.
I would do something like this. Let's assume that you are using dynamoDB and the aws sdk provides *dynamodb.DynamoDB
. Depending on your db, change this variable.
type serviceConnection struct {
db *dynamoDB.DynamoDB
// You can have all services declared here
// which you want to use it in your controller
}
initDatabaseConnection
which returns a handler to db, something like below.db := initDatabaseConnection()
-> returns *dynamodb.DynamoDB
db
to a struct variable.conn := new(serviceConnection)
conn.db = db
r := gin.Default()
r.GET("/books", conn.BooksController)
As you see, the gin handler is a controller method which has your struct instance as a receiver.
serviceConnection
struct receiver.func (conn *serviceConnection) BooksController(c *gin.Context) {
books := getBooks(conn.db)
}
As you see here, you have access to all the serviceConnection
struct variables and you can use them in your controller.
Upvotes: 0
Reputation: 19
I like wildneuro's example but would do a one liner to setup the handler
package main
import (
"log"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/mattn/go-sqlite3"
)
type HandlerA struct {
Db gorm.DB
}
func (this *HandlerA) Get(c *gin.Context) {
log.Info("[%#f]", this.Db)
// do your thing here...
}
func main() {
r := gin.New()
// Init, should be separate, but it's ok for this sample:
db, err := gorm.Open("sqlite3", "./example.db")
if err != nil {
log.Fatal(err)
}
r := gin.New()
Group := r.Group("api/v1/")
{
Group.GET("/storage", (&HandlerA{Db: db}).Get)
}
r.Run(":8080")
}
Upvotes: 1
Reputation: 24300
I would avoid stuffing 'application scoped' dependencies (e.g. a DB connection pool) into a request context. Your two 'easiest' options are:
*sql.DB
is thread-safe.gin.HandlerFunc
e.g.
// SomeHandler returns a `func(*gin.Context)` to satisfy Gin's router methods
// db could turn into an 'Env' struct that encapsulates all of your
// app dependencies - e.g. DB, logger, env vars, etc.
func SomeHandler(db *sql.DB) gin.HandlerFunc {
fn := func(c *gin.Context) {
// Your handler code goes in here - e.g.
rows, err := db.Query(...)
c.String(200, results)
}
return gin.HandlerFunc(fn)
}
func main() {
db, err := sql.Open(...)
// handle the error
router := gin.Default()
router.GET("/test", SomeHandler(db))
router.Run(":8080")
}
Upvotes: 61
Reputation: 55
Alright, I have given you a simple example. It should work. You can extend it as per your need
func main() {
router := gin.Default()
router.GET("/test/:id/:name", testRouteHandler)
router.Run(":8080")
}
func testRouteHandler(c *gin.Context) {
id := c.Params.ByName("id")
name := c.Params.ByName("name")
}
Now you will have to call your handler as below http://localhost:8080/test/1/myname
Upvotes: -1
Reputation: 1321
Using the link i posted on comments, I have created a simple example.
package main
import (
"log"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/mattn/go-sqlite3"
)
// ApiMiddleware will add the db connection to the context
func ApiMiddleware(db gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("databaseConn", db)
c.Next()
}
}
func main() {
r := gin.New()
// In this example, I'll open the db connection here...
// In your code you would probably do it somewhere else
db, err := gorm.Open("sqlite3", "./example.db")
if err != nil {
log.Fatal(err)
}
r.Use(ApiMiddleware(db))
r.GET("/api", func(c *gin.Context) {
// Don't forget type assertion when getting the connection from context.
dbConn, ok := c.MustGet("databaseConn").(gorm.DB)
if !ok {
// handle error here...
}
// do your thing here...
})
r.Run(":8080")
}
This is just a simple POC. But i believe it's a start. Hope it helps.
Upvotes: 42