0

I can't figure out how to solve this panic because I'm really new in Go, particularly in pointer, so bear with me. I tracked the panic message and found out that the panic is in gorm Create() in my repository package.

This is my database connection

var (
    Instance *gorm.DB
)

func Connect() {
    if err := godotenv.Load(); err != nil {
        panic(err.Error())
    }

    dbUser := os.Getenv("DB_USERNAME")
    dbPass := os.Getenv("DB_PASSWORD")
    dbHost := os.Getenv("DB_HOST")
    dbName := os.Getenv("DB_NAME")

    dsn := fmt.Sprintf("%s:%s@tcp(%s:3303)/%s?charset=utf8mb4&parseTime=True&loc=Local", dbUser, dbPass, dbHost, dbName)
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
        Logger: logger.Default.LogMode(logger.Info),
    })

    if err != nil {
        panic("Error connecting database : " + err.Error())
    }

    Instance = db
}

func CloseDB() {
    conn, err := Instance.DB()
    if err != nil {
        panic("Error closing database : " + err.Error())
    }

    conn.Close()
}

My auth repository

type AuthRepository interface {
    Create(user *entity.User) *entity.User
}

type authConnection struct {
    db *gorm.DB
}

func NewAuthRepository(db *gorm.DB) AuthRepository {
    return &authConnection{db}
}

func (c *authConnection) Create(user *entity.User) *entity.User {
    newPassword, err := hashPassword(user)
    if err != nil {
        return nil
    }

    user.Password = newPassword
    result := c.db.Create(user)
    if result == nil {
        return nil
    }

    return user

}

My auth service that uses auth repository

var authRepository = repository.NewAuthRepository(database.Instance)

type AuthService struct {
    Repository repository.AuthRepository
}

func (s *AuthService) Register(user *entity.User) (*entity.User, error) {
    newUser := s.Repository.Create(user)
    if newUser == nil {
        return nil, errors.New("failed registering user")
    }

    return newUser, nil
}

func NewAuthService() *AuthService {
    return &AuthService{
        Repository: authRepository,
    }
}

Last one, my auth controller


var (
    authService = service.NewAuthService()
)

func RegisterUser(c *gin.Context) {
    var user entity.User

    if err := c.ShouldBindJSON(&user); err != nil {
        handler.BuildErrorResponse(c, err)
        return
    }

    record, err := authService.Register(&user)
    if err != nil {
        handler.BuildErrorResponse(c, err)
        return
    }

    handler.BuildCreatedResponse(c, record)
}

And then the error

runtime error: invalid memory address or nil pointer dereference
/usr/lib/golang/src/runtime/panic.go:212 (0x435eda)
        panicmem: panic(memoryError)
/usr/lib/golang/src/runtime/signal_unix.go:734 (0x44e892)
        sigpanic: panicmem()
/home/thoriqadillah/Development/Golang/pkg/mod/gorm.io/gorm@v1.23.6/finisher_api.go:18 (0x5d9226)
        (*DB).Create: if db.CreateBatchSize > 0 {
/home/thoriqadillah/Development/Golang/src/jwt/repository/auth_repository.go:28 (0xa19d04)
        (*authConnection).Create: result := c.db.Create(user)
/home/thoriqadillah/Development/Golang/src/jwt/service/auth_service.go:18 (0xa19ee1)
        (*AuthService).Register: newUser := s.Repository.Create(user)
/home/thoriqadillah/Development/Golang/src/jwt/controller/auth_controller.go:22 (0xa1a0ae)
        RegisterUser: record, err := authService.Register(&user)
/home/thoriqadillah/Development/Golang/pkg/mod/github.com/gin-gonic/gin@v1.8.1/context.go:173 (0x9d9e39)
        (*Context).Next: c.handlers[c.index](c)
/home/thoriqadillah/Development/Golang/pkg/mod/github.com/gin-gonic/gin@v1.8.1/recovery.go:101 (0x9d9e20)
        CustomRecoveryWithWriter.func1: c.Next()
/home/thoriqadillah/Development/Golang/pkg/mod/github.com/gin-gonic/gin@v1.8.1/context.go:173 (0x9d9013)
        (*Context).Next: c.handlers[c.index](c)
/home/thoriqadillah/Development/Golang/pkg/mod/github.com/gin-gonic/gin@v1.8.1/logger.go:240 (0x9d8fd2)
        LoggerWithConfig.func1: c.Next()
/home/thoriqadillah/Development/Golang/pkg/mod/github.com/gin-gonic/gin@v1.8.1/context.go:173 (0x9cedcf)
        (*Context).Next: c.handlers[c.index](c)
/home/thoriqadillah/Development/Golang/pkg/mod/github.com/gin-gonic/gin@v1.8.1/gin.go:616 (0x9cedb5)
        (*Engine).handleHTTPRequest: c.Next()
/home/thoriqadillah/Development/Golang/pkg/mod/github.com/gin-gonic/gin@v1.8.1/gin.go:572 (0x9ce879)
        (*Engine).ServeHTTP: engine.handleHTTPRequest(c)
/usr/lib/golang/src/net/http/server.go:2868 (0x7d7582)
        serverHandler.ServeHTTP: handler.ServeHTTP(rw, req)
/usr/lib/golang/src/net/http/server.go:1933 (0x7d29ac)
        (*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req)
/usr/lib/golang/src/runtime/asm_amd64.s:1371 (0x46df60)
        goexit: BYTE    $0x90   // NOP

I'm assuming that the error comes from gorm Create(). I tried debugging it with printing the the user parameter in auth repository inside the function with fmt.Println(user), fmt.Println(&user), fmt.Println(*user), and the fmt.Println(&user) gives memory address. But even I change the Create(user) with Create(&user) it still doesn't work. I tried directly use the gorm in the controller with database.Instance.Create(&user) and it worked, but I want to learn to make my code a bit modular

newtocoding
  • 97
  • 2
  • 5

2 Answers2

4

Actually, you got an error in the string:

db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{

Because of operator :=
GO creates a new local variable db and uses it instead of the global one.
To fix this just change this line to:

var err error
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{

Or, in your case, to this one:

var err error
Instance, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
0

Avoid global variables and implement dependency injection.

func NewAuthService(repository AuthRepository) *AuthService {
    return &AuthService{
        Repository: repository,
    }
}

func RegisterUser(c *gin.Context, authService AuthService) {
    ...
}


Dmitry Harnitski
  • 5,838
  • 1
  • 28
  • 43
  • I'm sorry that i was unavailable to comment your answer for the last 4 days. if I was to give my controller the interface parameter, how do I use it in my router? If I'm not mistaken, gin router accept `gin.HandlerFunc` as a second parameter, which is my controller, so it should be like `auth.POST("/register", controller.RegisterUser)`. How do i pass the interface parameter to the controller and use it in the router? – newtocoding Jun 16 '22 at 06:52
  • 1
    Most maintenable way - convert your RegisterUser function into struct method and use router GET, POST methods: https://stackoverflow.com/a/60324692/1420332 – Dmitry Harnitski Jun 16 '22 at 13:16
  • your'e right, that's pretty nice to have a struct as public method. Anywaw, as you mentioned before, avoid using global variable is true. I change my `Connect()` to `GetInstance()` and return the db instance instead and use it in my repository, and change the `Instance` var to local var for closing the db. Thank you sir! – newtocoding Jun 17 '22 at 07:49
  • Glad to be helpful! Happy Coding! – Dmitry Harnitski Jun 17 '22 at 12:21