I need to pass a database instance from my route.go file to a controller file in gin.
I tried passing the Server Struct But I'm getting a circular import. Also, I cant use any of *sql.DB function even in the same file. I am using melkey's go-blueprint package.
This is my server.go file
er
import (
"backend/internal/database"
"fmt"
"net/http"
"os"
"strconv"
"time"
_ "github.com/joho/godotenv/autoload"
)
type Server struct {
port int
db database.Service
}
func NewServer() *http.Server {
port, _ := strconv.Atoi(os.Getenv("PORT"))
NewServer := &Server{
port: port,
db: database.New(),
}
// Declare Server config
server := &http.Server{
Addr: fmt.Sprintf(":%d", NewServer.port),
Handler: NewServer.RegisterRoutes(),
IdleTimeout: time.Minute,
ReadTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
}
return server
}
this is my route.go file
package server
import (
"backend/internal/controllers"
"net/http"
"github.com/gin-gonic/gin"
)
func (s *Server) RegisterRoutes() http.Handler {
r := gin.Default()
r.GET("/", s.HelloWorldHandler)
r.GET("/health", s.healthHandler)
r.GET("/test", controllers.TestController)
auth := r.Group("/auth")
AuthRoute(auth)
return r
}
func (s *Server) HelloWorldHandler(c *gin.Context) {
resp := make(map[string]string)
resp["message"] = "Hello World"
// Unable to use s.db.Query() or something
c.JSON(http.StatusOK, resp)
}
func (s *Server) healthHandler(c *gin.Context) {
c.JSON(http.StatusOK, s.db.Health())
}
Controller File
package controllers
import (
"net/http"
"github.com/gin-gonic/gin"
)
func TestController(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Hello World from the controllers package",
})
}
And this is the Database.go file
package database
import (
"context"
"database/sql"
"fmt"
"log"
"os"
"time"
_ "github.com/jackc/pgx/v5/stdlib"
_ "github.com/joho/godotenv/autoload"
)
type Service interface {
Health() map[string]string
}
type service struct {
db *sql.DB
}
var (
database = os.Getenv("DB_DATABASE")
password = os.Getenv("DB_PASSWORD")
username = os.Getenv("DB_USERNAME")
port = os.Getenv("DB_PORT")
host = os.Getenv("DB_HOST")
dbInstance *service
)
func New() Service {
// Reuse Connection
if dbInstance != nil {
return dbInstance
}
connStr := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable", username, password, host, port, database)
db, err := sql.Open("pgx", connStr)
if err != nil {
log.Fatal(err)
}
dbInstance = &service{
db: db,
}
// We Migrate all the data by calling the migrate Function
Migrate(dbInstance)
return dbInstance
}
func (s *service) Health() map[string]string {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
err := s.db.PingContext(ctx)
if err != nil {
log.Fatalf(fmt.Sprintf("db down: %v", err))
}
return map[string]string{
"message": "It's healthy",
}
}
I'll recommend you to use dependency injection by passing DB where is needed. allow me to propose the following refactoring.
database.go
package main
import (
"database/sql"
"fmt"
_ "github.com/lib/pq"
)
func NewDatabase(dbUser, dbPassword, dbHost, dbName, dbPort string) (*sql.DB, error) {
connectionStr := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable", dbUser, dbPassword, dbHost, dbPort, dbName)
return sql.Open("postgres", connectionStr)
}
service.go
package main
import "database/sql"
type User struct {
Id string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Gender string `json:"gender"`
}
type UserLoader interface {
GetAll() ([]User, error)
GetUserByEmail(string) (User, error)
}
type userLoader struct {
dbCon *sql.DB
}
func NewUserLoader(dbCon *sql.DB) UserLoader {
return &userLoader{dbCon: dbCon}
}
func (l *userLoader) GetAll() ([]User, error) {
rows, err := l.dbCon.Query("SELECT * FROM users")
if err != nil {
return nil, err
}
defer rows.Close()
users := make([]User, 0)
for rows.Next() {
var user User
if err := rows.Scan(&user.Id, &user.Name, &user.Email, &user.Gender); err != nil {
return nil, err
}
users = append(users, user)
}
return users, nil
}
func (l *userLoader) GetUserByEmail(email string) (User, error) {
panic("implement me")
}
router.go
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func NewRouter(userLoader UserLoader) *gin.Engine {
r := gin.Default()
r.GET("/", HelloWorld)
r.GET("/health", Health)
api := r.Group("/api")
api.GET("/users", GetAllUsers(userLoader))
return r
}
func GetAllUsers(loader UserLoader) gin.HandlerFunc {
return func(c *gin.Context) {
users, err := loader.GetAll()
if err != nil {
c.AbortWithStatus(http.StatusInternalServerError)
}
c.JSON(http.StatusOK, users)
}
}
func HelloWorld(c *gin.Context) {
c.JSON(200, gin.H{"message": "Hello World from the controllers package"})
}
func Health(c *gin.Context) {
c.JSON(200, gin.H{"status": "ok"})
}
main.go
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"os"
)
func main() {
database := os.Getenv("DB_DATABASE")
dbPassword := os.Getenv("DB_PASSWORD")
dbUsername := os.Getenv("DB_USERNAME")
dbPort := os.Getenv("DB_PORT")
host := os.Getenv("DB_HOST")
serverPort := os.Getenv("SERVER_PORT")
dbCon, err := NewDatabase(dbUsername, dbPassword, host, database, dbPort)
if err != nil {
panic(err)
}
userLoader := NewUserLoader(dbCon)
router := gin.Default()
router.Use(gin.Recovery())
router.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{})
})
NewRouter(userLoader).Run(fmt.Sprintf(":%s", serverPort))
}