Search code examples
mysqlgogo-gormgo-gin

Error uploading file while creating a new user in Golang


Below is the controller to create new user. I have used it without the code of file upload(placed string instead). It's working perfect. But this code shows below error. I am not getting any specific reason or handled error.

controller

func UserCreate(c *gin.Context) {
    // Parse the form data with a higher maxMemory and parseMultipartForm
    if err := c.Request.ParseMultipartForm(10 << 20); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": "Failed to parse form data: " + err.Error(),
        })
        return
    }

    // Get data from request body
    var body struct {
        Username  string `form:"username"`
        FirstName string `form:"first_name"`
        LastName  string `form:"last_name"`
        Email     string `form:"email"`
        About     string `form:"about"`
        U_RoleID uint   `form:"role_id"`
        IsActive bool   `form:"is_active"`
        Password string `form:"password"`
    }
    if err := c.Bind(&body); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": "Failed to read request body: " + err.Error(),
        })
        return
    }

    // Get the file of avatar
    file, err := c.FormFile("avatar")
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": "Failed to upload image",
        })
        return
    }
    //Save the avatar file
    extension := filepath.Ext(file.Filename)
    timestamp := time.Now().Unix()
    NewFileName := fmt.Sprintf("%s_%d%s", body.Username, timestamp, extension)
    if err := c.SaveUploadedFile(file, "/../assets/uploads/"+NewFileName).Error; err != nil {
        // Log the error
        fmt.Println("Error saving avatar file:", err)

        c.JSON(http.StatusInternalServerError, gin.H{
            "error": "Failed to save avatar file",
        })
        return
    }

    //Hash password with bcrypt
    hash, err := bcrypt.GenerateFromPassword([]byte(body.Password), 10)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": "Failed to hash password",
        })
        return
    }

    //create a User
    user := models.User{Username: body.Username, FirstName: body.FirstName, LastName: body.LastName, Email: body.Email, About: body.About, Avatar: NewFileName, U_RoleID: body.U_RoleID, IsActive: body.IsActive, Password: string(hash)}
    result := initializers.DB.Create(&user)

    if result.Error != nil {
        // Log the error
        fmt.Println("Error creating user:", result.Error)

        c.JSON(http.StatusBadRequest, gin.H{
            "error": "Failed to create new user",
        })
        return
    }

    //return User data
    c.JSON(http.StatusOK, gin.H{
        "User": user,
    })
}

Error:

runtime error: invalid memory address or nil pointer dereference

LoadEnvVariables

package initializers

import (
    "fmt"

    "github.com/joho/godotenv"
)

func LoadEnvVariables() {
    err := godotenv.Load(".env")
    if err != nil {
        fmt.Println("Error loading .env file")
    }

}

Initializers package:

package initializers

import (
    "log"
    "os"

    "github.com/joho/godotenv"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

var DB *gorm.DB

func ConnectToDB() *gorm.DB {
    //Load .env file
    err := godotenv.Load(".env")
    if err != nil {
        log.Fatal("Error reading .env file: ", err)
    }

    //Get Environment Variables
    dbUser := os.Getenv("DB_USER")
    dbPassword := os.Getenv("DB_PASSWORD")
    dbHost := os.Getenv("DB_HOST")
    dbPort := os.Getenv("DB_PORT")
    dbName := os.Getenv("DB_NAME")

    //MySQL connection string
    dsn := dbUser + ":" + dbPassword + "@tcp(" + dbHost + ":" + dbPort + ")/" + dbName + "?charset=utf8mb4&parseTime=True&loc=Local"

    // Connect to MySQL database
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic(err.Error())
    }

    // Assign the database instance to the global variable
    DB = db

    return DB
}

func CloseDB() error {

    if DB != nil {
        sqlDB, err := DB.DB()
        if err != nil {
            return err
        }
        sqlDB.Close()
    }
    return nil
}

Below is the model related to it:

type User struct {
    gorm.Model
    Username  string `gorm:"type:varchar(64);unique"`
    FirstName string `gorm:"type:varchar(64)"`
    LastName  string `gorm:"type:varchar(64)"`
    Email     string `gorm:"type:varchar(128);unique"`
    About     string `gorm:"type:text"`
    Avatar    string `gorm:"type:text"`
    U_RoleID  uint   `gorm:"index"`
    IsActive  bool
    Password  string `gorm:"type:varchar(255)"`
}

Just for easy to test, below is the route

r.POST("/api/user", controllers.UserCreate)

Used tools (Gin, Gorm, Bcrypt, MySQL)


Solution

  • You do have an issue at here

    err := c.SaveUploadedFile(file, "/../assets/uploads/"+NewFileName).Error
    

    when there isn't an error returned from SaveUploadedFile (which means got nil), then you won't able to access Error from nil object.

    change this into

    if err := c.SaveUploadedFile(file, "/../assets/uploads/"+NewFileName); err != nil {
     .... 
    }
    

    Also please note, Error is a method

    // The error built-in interface type is the conventional interface for
    // representing an error condition, with the nil value representing no error.
    type error interface {
        Error() string
    }