I have two S3 bucket services, one is sponsored by @Licson, another is Backblaze B2. I usally upload my static asset file or resource file to them.

Today, I want to make a RESTFul API to upload files to S3.

Let’s do it!

Environment

Package list:

github.com/aws/aws-sdk-go v1.43.28
github.com/gin-contrib/cors v1.3.1
github.com/gin-gonic/gin v1.7.7
github.com/joho/godotenv v1.4.0

Write a gin router

Before start, Let me quick to introduce the gin framework.

Gin is a web framework for Go. It’s a high performance and high quality framework for building web applications.

Since it can easily return JSON data, so we can use it to build a RESTful API.

First, use go mod init to create a module.

go mod init test-go

Then, let’s import the gin framework and the cors middleware.

go get github.com/gin-gonic/gin
go get github.com/gin-contrib/cors

Create main.go and add the following code:

package main

import (
	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
)

func main() {
	// Create a gin router
	// gin.SetMode(gin.ReleaseMode)
	router := gin.New()
	router.Use(gin.Logger(), gin.Recovery())

	// Set CORS
	config := cors.DefaultConfig()
	config.AllowOrigins = []string{"*"}
	router.Use(cors.New(config))

	// Run
	router.Run("127.0.0.1:29750")
}

Great, now we have a gin router.
You can use the following command to run the server:

# Run application
go run test-go

# Use curl to test
$ curl http://127.0.0.1:29750

# It should return the below data.
404 page not found

Add API Endpoint

Once we have the router, we can add the API endpoint.

func UploadFile(c *gin.Context) {
	c.JSON(200, gin.H{"message": "Pong!"})
}

func main() {
    ...
    ...
    ...
    apiv1 := router.Group("/api/v1/") // Create a group
    apiv1.POST("/upload", UploadFile) // Add the endpoint
    ...
}

In the above code, we create a group and add the endpoint with its function.
We can use below command to test the endpoint:

$ curl -X POST http://127.0.0.1:29750/api/v1/upload

# It should return the below data.
{"message":"Pong!"}

Design the API function

// Define return result
type Result struct {
	Success   bool
	Message   string
	File_Name string
}

// Create folder as storage cache
func CreateFolder(FolderName string) bool {
	returnCheck := false
	if _, err := os.Stat(FolderName); os.IsNotExist(err) {
		err = os.Mkdir(FolderName, 0755)
		if err != nil {
			// fmt.Println(err)
			returnCheck = false
		} else {
			returnCheck = true
		}
	} else {
		returnCheck = true
	}
	return returnCheck
}

// Process file upload
func UploadFile(c *gin.Context) {
    // Pre define variables
	var r Result
	var returnCheck bool
	var returnMessage string
	returnStatusCode := 200

    // Get file information
	file, header, err := c.Request.FormFile("upload_file")
	if err != nil {
		r = Result{false, "Bad Request!", ""}
		c.JSON(400, r)
		return
	}

	filename := header.Filename

    // Check if the folder exist
	if !os.IsNotExist(err) && CreateFolder("tmp") {

        // Copy file to the folder
		out, err := os.Create("tmp/" + filename)
		if err != nil {
			returnStatusCode = 400
			returnCheck = false
			returnMessage = "Error!"
		}
		defer out.Close()

		_, err = io.Copy(out, file)
		if err != nil {
            // If error, return 400 error
			returnStatusCode = 400
			returnCheck = false
			returnMessage = "Error!"
		} else {
            // If success, return 200 success
			returnStatusCode = 201
			returnCheck = true
			returnMessage = "Success!"
		}
	} else {
        // return failed to create the folder
		returnStatusCode = 400
		returnCheck = false
		returnMessage = "Failed to create the tmp folder!"
	}
    // return JSON type
	r = Result{returnCheck, returnMessage, filename}
	c.JSON(returnStatusCode, r)
}

I have already published a repository on GitHub file-upload-to-s3. Feel free to check it if needed.