databasegoconnectiongo-gorm

golang GORM and Gin Unable to create object with association


I'm using GORM for the ORM with my Golang API to communicate with my DB.

But for the creation with of an an entity with association on the database it's failed with the code (DB.Create(&data)) :

2023/11/01 20:44:14 [Recovery] 2023/11/01 - 20:44:14 panic recovered:
POST /v1/product/products HTTP/1.1
Host: localhost:8080
Accept: */*
Content-Length: 589
Content-Type: application/json
User-Agent: curl/8.4.0


runtime error: invalid memory address or nil pointer dereference
/usr/lib/go/src/runtime/panic.go:261 (0x452d97)
        panicmem: panic(memoryError)
/usr/lib/go/src/runtime/signal_unix.go:861 (0x452d65)
        sigpanic: panicmem()
/home/grimm/go/pkg/mod/gorm.io/gorm@v1.25.5/finisher_api.go:18 (0x926e7c)
        (*DB).Create: if db.CreateBatchSize > 0 {
/home/grimm/Documents/beeskill/website/back_end/API_website/main.go:122 (0x99cfab)
        CreatingProduct: DB.Create(&data)
/home/grimm/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/context.go:174 (0x8c9ad9)
        (*Context).Next: c.handlers[c.index](c)
/home/grimm/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/recovery.go:102 (0x8c9ac7)
        CustomRecoveryWithWriter.func1: c.Next()
/home/grimm/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/context.go:174 (0x8c8c7d)
        (*Context).Next: c.handlers[c.index](c)
/home/grimm/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/logger.go:240 (0x8c8c4c)
        LoggerWithConfig.func1: c.Next()
/home/grimm/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/context.go:174 (0x8c7d3a)
        (*Context).Next: c.handlers[c.index](c)
/home/grimm/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/gin.go:620 (0x8c79cd)
        (*Engine).handleHTTPRequest: c.Next()
/home/grimm/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/gin.go:576 (0x8c74fc)
        (*Engine).ServeHTTP: engine.handleHTTPRequest(c)
/usr/lib/go/src/net/http/server.go:2938 (0x6a35ed)
        serverHandler.ServeHTTP: handler.ServeHTTP(rw, req)
/usr/lib/go/src/net/http/server.go:2009 (0x69f4d3)
        (*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req)
/usr/lib/go/src/runtime/asm_amd64.s:1650 (0x46ed20)
        goexit: BYTE    $0x90   // NOP

The code used :

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

type CategoryProduct struct {
    gorm.Model
    Title       string `json:"title" gorm:"not null"`
    Description string `json:"description"`
}

type CreateCategoryProduct struct {
    gorm.Model
    Title       string `json:"title" binding:"required"`
    Description string `json:"description" binding:"required"`
}

// --------------------------------------------------------
// --------------------------------------------------------
// --------------------------------------------------------
// --------------------------------------------------------
// --------------------------------------------------------
type PriceYearProduct struct {
    gorm.Model
    Price       float64 `json:"price" gorm:"not null"`
    Description string  `json:"description"`
}

type CreatePriceYearProduct struct {
    gorm.Model
    Price       float64 `json:"price" binding:"required"`
    Description string  `json:"description" binding:"required"`
}

// --------------------------------------------------------
// --------------------------------------------------------
// --------------------------------------------------------
// --------------------------------------------------------
// --------------------------------------------------------
type PriceMounthProduct struct {
    gorm.Model
    Price       float64 `json:"price" gorm:"not null"`
    Description string  `json:"description"`
}

type CreatePriceMounthProduct struct {
    gorm.Model
    Price       float64 `json:"price" binding:"required"`
    Description string  `json:"description" binding:"required"`
}

// --------------------------------------------------------
// --------------------------------------------------------
// --------------------------------------------------------
// --------------------------------------------------------
// --------------------------------------------------------
type Product struct {
    gorm.Model
    Name               string             `json:"name" gorm:"not null"`
    Description        string             `json:"description" gorm:"not null"`
    PriceMounthProduct PriceMounthProduct `json:"pricemounth"`
    PriceYearProduct   PriceYearProduct   `json:"priceyear"`
    CategoryProduct    CategoryProduct    `json:"category"`
}

type CreateProduct struct {
    gorm.Model
    Name               string             `json:"name" binding:"required"`
    Description        string             `json:"description" binding:"required"`
    PriceMounthProduct PriceMounthProduct `json:"pricemounth" binding:"required"`
    PriceYearProduct   PriceYearProduct   `json:"priceyear" binding:"required"`
    CategoryProduct    CategoryProduct    `json:"category" binding:"required"`
}

var DB *gorm.DB

func ConnectDatabase() {

    database, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})

    if err != nil {
        panic("Failed to connect to database!")
    }
    // For the product and dependancies of them
    err = database.AutoMigrate(&CategoryProduct{}, &PriceYearProduct{}, &PriceMounthProduct{})
    if err != nil {
        return
    }
    err = database.AutoMigrate(&Product{})
    if err != nil {
        return
    }

    DB = database
}

// POST /v1/products
// Create a product
func CreatingProduct(c *gin.Context) {
    // Validate input
    var input CreateProduct
    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    data := Product{
        Name:               input.Name,
        Description:        input.Description,
        PriceYearProduct:   input.PriceYearProduct,
        PriceMounthProduct: input.PriceMounthProduct,
        CategoryProduct:    input.CategoryProduct,
    }
    DB.Create(&data)

    c.JSON(http.StatusOK, gin.H{"data": data})

}

func Routes() {
    var router = gin.Default()

    v1 := router.Group("/v1")
    {
        //Product CRUD
        product := v1.Group("/product")
        product.POST("/products", CreatingProduct)

    }

    err := router.Run(":8080")
    if err != nil {
        return
    }
}

func main() {
    ConnectDatabase()
    Routes()

}

And the curl command to POST some data:

curl http://localhost:8080/v1/product/products --request "POST" --header "Content-Type: application/json" --data @file.json

The content of the file.json :

{
    "name": "airplane simulation",
    "description": "airplane simulation for everybody, childrens to adults.",
    "priceyear": {
      "price": 44.99,
      "description": "pay for a year to access on irplane simulation for everybody, childrens to adults."
    },
    "pricemounth": {
      "price": 4.99,
      "description": "pay for a mounth to access on irplane simulation for everybody, childrens to adults."
    },
    "category": {
      "title": "Airplane",
      "description": "Information cataegorized on airplane."
    }
}

I've look the offical documentation but no more information... Tried to debug with the the variable on the creation but all thing seem to be alrights...


Solution

  • Run you code got error

    [error] invalid field found for struct
     main.Product's field PriceMounthProduct: 
    define a valid foreign key for 
    relations or implement the Valuer/Scanner interface
    

    the error message at the very fist line before gin log, may be you miss it.

    So fix it ,add the right foreignkey gorm tag in the Product struct it's will be ok.

    
    type Product struct {
        gorm.Model
        Name               string             `json:"name" gorm:"not null"`
        Description        string             `json:"description" gorm:"not null"`
        PriceMounthProduct PriceMounthProduct `json:"pricemounth" gorm:"foreignkey:ID"`
        PriceYearProduct   PriceYearProduct   `json:"priceyear" gorm:"foreignkey:ID"`
        CategoryProduct    CategoryProduct    `json:"category" gorm:"foreignkey:ID"`
    }
    

    and run

    curl http://localhost:8080/v1/product/products --request "POST" --header "Content-Type: application/json" --data @info.json
    

    got

    {"data":{"ID":1,"CreatedAt":"2023-11-02T13:37:24.052228+08:00","UpdatedAt":"2023-11-02T13:37:24.052228+08:00","DeletedAt":null,"name":"airplane simulation","description":"airplane simulation for everybody, childrens to adults.","pricemounth":{"ID":1,"CreatedAt":"2023-11-02T13:37:24.054792+08:00","UpdatedAt":"2023-11-02T13:37:24.054792+08:00","DeletedAt":null,"price":4.99,"description":"pay for a mounth to access on irplane simulation for everybody, childrens to adults."},"priceyear":{"ID":1,"CreatedAt":"2023-11-02T13:37:24.056352+08:00","UpdatedAt":"2023-11-02T13:37:24.056352+08:00","DeletedAt":null,"price":44.99,"description":"pay for a year to access on irplane simulation for everybody, childrens to adults."},"category":{"ID":1,"CreatedAt":"2023-11-02T13:37:24.056585+08:00","UpdatedAt":"2023-11-02T13:37:24.056585+08:00","DeletedAt":null,"title":"Airplane","description":"Information cataegorized on airplane."}}}%