如何正确使控制器指向Golang中的路由?

I am new in Golang and need some help. I am tring to create REST API web service without ORM.

Right now I am successfully connected to PostgreSQL database. In database I have table which called factors. I want to create CRUD operations. The problem is with controllers logic.

main.go:

package main

import (
    "github.com/gorilla/mux"
    "log"
    "net/http"
    "rest_api/configurations"
    "rest_api/controllers"
)

func main()  {
    db, err := configurations.PostgreSQLDatabase()
    if err != nil {
        log.Fatal(err)
    }

    router := mux.NewRouter()

    router.StrictSlash(true)

    subrouter := router.PathPrefix("/api").Subrouter()

    subrouter.HandleFunc("/factors", controllers.GetFactors(db)).Methods("GET")

    log.Fatal(http.ListenAndServe(":8000", router))
}

models/factors.go:

package models

type Factor struct {
    ID int `json:"id"`
    Name string `json:"name"`
}

How correctly looks like the GetFactors controller? Can someone show me please. For instance I pass db object to GetFactors controller as in the example below. Unfortunately it seems like it's incorrect.

controllers/factors.go:

func GetFactors(db *sql.DB, w http.ResponseWriter, req *http.Request) {
    // some code
}

configurations/PostgreSQL.go:

func PostgreSQLDatabase() (*sql.DB, error) {
    // Load environment variables from ".env" file.
    err := godotenv.Load(".env")
    if err != nil {
        log.Fatal(err)
    }

    // Initialize database-related variables.
    dbUser := os.Getenv("PostgreSQL_USER")
    dbPassword := os.Getenv("PostgreSQL_PASSWORD")
    dbHost := os.Getenv("PostgreSQL_HOST")
    dbName := os.Getenv("PostgreSQL_DB_NAME")
    dbURL := fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=disable", dbUser, dbPassword, dbHost, dbName)

    // Create PostgreSQL database connection pool.
    db, err := sql.Open("postgres", dbURL)
    if err != nil {
        return nil, err
    }

    // Ping PostgreSQL database to make sure it's alive.
    err = db.Ping()
    if err != nil {
        log.Fatal(err)
    } else {
        log.Println("Web service successfully connected to remote PostgreSQL database.")
    }

    return db, nil
}

A pattern I like to use is to define your own Router struct that has a mux.Router as a field as well as encapsulates things like your database connection, application config and etc.

I find doing it this way makes it easily update your routes when they require different resources and development proceeds.

First create a router object that takes in the database connection on creation and makes it available to all routes you wish to use.

router.go

package main

import (
    "net/http"
    "database/sql"

    "github.com/gorilla/mux"
)

type Router struct {
    router *mux.Router
    db *sql.DB
}

func NewRouter(db *sql.DB) (*Router, error) {
    router := mux.NewRouter()
    router.StrictSlash(true)
    subrouter := router.PathPrefix("/api").Subrouter()

    r := &Router{
        router: router,
        db: db,
    }

    subrouter.HandleFunc("/factors", r.GetFactors).Methods(http.MethodGet)

    return r, nil
}

func (r *Router) GetFactors(w http.ResponseWriter, req *http.Request) {
    // Now you can access your database via `r.db`
}

// Needed so we can pass our custom router to ListenAndServe.
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    r.router.ServeHTTP(w, req)
}

Then in main.go you can simply create your custom router, passing it your database connection. Then the custom router can be passed directly to ListenAndServe.

main.go

package main

import (
    "log"
    "net/http"
    "rest_api/configurations"
    "rest_api/controllers"
)

func main()  {
    db, err := configurations.PostgreSQLDatabase()
    if err != nil {
        log.Fatal(err)
    }

    router, err := NewRouter(db)
    if err != nil {
        log.Fatalf("error initializing router: %v", err)
    }

    log.Fatal(http.ListenAndServe(":8000", router))
}

Hopefully this helps.

Your func GetFactors must looks like:

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

and in main file you must have:

subrouter.HandleFunc("/factors", controllers.GetFactors).Methods("GET")

and with purpose to get DB connection you must have func like GetDB in your package "rest_api/configurations".

In "rest_api/configurations" you must have something like:

var db *PostgreSQLDatabase

func init() {
    var err error
    db, err = configurations.PostgreSQLDatabase()
    if err != nil {
        log.Fatal(err)
    }
}

func GetDB() *PostgreSQLDatabase {
  return db
}

There is no correct way, it mostly opinion-based.

The semantic of HandlerFunc function should be like func(w http.ResponseWriter, r *http.Request), in order to pass database you can use closures, here is an example.

main.go

// ... some code here 
subrouter.HandleFunc("/factors", controllers.GetFactors(db)).Methods("GET")
// ... some code here

controllers/factors.go

func GetFactors(db *sql.DB) http.HandlerFunc {
  return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
    // some code
  })
}

Another option:

I'm not quite sure about this, but you can adjucts it to your needs. Initialize a Controller struct and pass db to it:

main.go

// ... some code here 
db, err := configurations.PostgreSQLDatabase()
if err != nil {
    log.Fatal(err)
}
ctrl := controllers.Controller{DB: db}
subrouter.HandleFunc("/factors", ctrl.GetFactors).Methods("GET")
// ... some code here

Denote a method on the Controller struct. Define a struct in the controllers

controllers/factors.go

type Controller struct {
  DB *PostgreSQLDatabase
}

func (c Controller) GetFactors(w http.ResponseWriter, req *http.Request) {
    // some code
    // c.DB.MySqlMethod()
}