1

I am trying to build a REST API with go-chi and Gorm.

I am not sure how I should pass the Gorm DB instance to the route handlers.

Or if I should create one instance per handler, which does not sound right to me.

Should I use middleware, dependency injection or other? What would be recommended pattern here?

package main

import (
    "encoding/json"
    "fmt"
    "github.com/go-chi/chi/v5"
    "log"
    "net/http"
    "os"
    "time"
)

func main() {
    r := chi.NewRouter()

    r.Get("/", indexHandler)


    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
        log.Printf("Defaulting to port %s", port)
    }

    db := Connect()
    migrations(db)
    logStartServer(port)
    log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), r))
}

func logStartServer(port string) {
    log.Printf("Listening on port %s", port)
    log.Printf("Open http://localhost:%s in the browser", port)
}

func indexHandler(w http.ResponseWriter, r *http.Request) {

    //How can I access db here?
    //result := db.Find(&users)

    policy := InsurancePolicy{ValidFrom: time.Now()}
    err := json.NewEncoder(w).Encode(policy)

    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
    }
}

blackgreen
  • 34,072
  • 23
  • 111
  • 129
aolivera
  • 512
  • 1
  • 4
  • 23

3 Answers3

8

Use methods instead of functions. This allows you to pass any information needed by the handlers using the receiver of those methods:

type MyHandler struct {
  DB *gorm.DB
}

func (m MyHandler) IndexHandler(w http.ResponseWriter, r *http.Request) {
  // Use m.DB here
}

In main:

handler:=mypkg.MyHandler{DB:gormDB}
r.Get("/", handler.IndexHandler)

In some cases, a closure makes more sense.

func GetIndexHandler(db *gorm.DB) func(http.ResponseWriter,*http.Request) {
   return func(w http.ResponseWriter,req *http.Request) {
     // Implement index handler here, using db
   }
}

func main() {
  ...
  r.Get("/", GetIndexHandler(db))
Burak Serdar
  • 46,455
  • 3
  • 40
  • 59
1

Declaring the DB instance as a global variable is quite convenient if your project is small.

A number of ways for organising DB access are documented here quite well. Pick the one which fits your needs.

Vaibhav Mishra
  • 415
  • 3
  • 10
-1

In the DB/query function itself. I personally make a separate package for controllers and a separate package for services. I handle all the request validation and HTTP stuff in the controller (which has my handler functions). Then, if everything checks out, I call a service package. The service package is the one that calls the DB as well as any other services or API integrations.

Yet, where ever you call the DB, usually you are calling into a db package that has a bunch of query functions with friendly names like db.GetAccountByID or something like that. Well, that db function is exactly where you pass the *sql.DB or *gorm.DB object.

An example would be...

package db

func GetAccountByID(id int, db *gorm.DB) (*model.Account, error) {
  if db == nil {
    db = conn // conn is the package level db connection object
  }      
  //...
}

Generally, when the server starts, I create the DB connection (which functions as a connection pool) and so it's not really necessary to pass it into the function. So, why do it? Well, it's because of testing. You don't want your DB handler reaching out to a package level DB connection object because it becomes more difficult to do isolated testing of that function.

So, this function signature gives you that testability and the initial if condition still uses that single central DB connection object if nil is passed in for the DB value, which is always is nil unless you are testing.

This is just one approach but one I've used successfully for years now.

GoForth
  • 573
  • 7
  • 10