Golang : gin tutorial - 4 (Create a multipart/form-data POST request)

·

3 min read

Golang : gin tutorial - 4 (Create a multipart/form-data POST request)

In the previous article we saw how exactly to create a POST request which consumes a request body. In this article we will see how to consume a multipart/form-data request using golang's gin-gonic and also how to write unit tests for it.

Step 1

Create controller function in the controller.go file which will consume the multipart/form-data request and read contents of the file.

The below controller shows how to Open a received file from the request, create a temp file and write data from the received file to it, and also close and delete the files which were created after exiting the controller function.

The controller function just returns the file contents.

func ConsumeFile(ctx *gin.Context) {
    fileHeader, err := ctx.FormFile("file")
    if err != nil {
        ctx.Error(err)
        return
    }

    //Open received file
    csvFileToImport, err := fileHeader.Open()
    if err != nil {
        ctx.Error(err)
        return
    }
    defer csvFileToImport.Close()

    //Create temp file
    tempFile, err := ioutil.TempFile("", fileHeader.Filename)
    if err != nil {
        ctx.Error(err)
        return
    }
    defer tempFile.Close()

    //Delete temp file after importing
    defer os.Remove(tempFile.Name())

    //Write data from received file to temp file
    fileBytes, err := ioutil.ReadAll(csvFileToImport)
    if err != nil {
        ctx.Error(err)
        return
    }
    _, err = tempFile.Write(fileBytes)
    if err != nil {
        ctx.Error(err)
        return
    }

    ctx.JSON(http.StatusOK, string(fileBytes))
}

Step 2

Register the above created consume file controller function to gin as an endpoint as it was did before in the previous article in the main.go file.

v1 := engine.Group("/api/v1")
{
    v1.GET("/contents", content.GetContents)
    v1.POST("/contents", content.PostContents)
    v1.POST("/contents/import", content.ConsumeFile)
}

Step 3

Now, the next step is to write tests for the new multipart/form-data endpoint. So, for that we need a file sample_success.txt in a directory with any name and for the sake of an example there is a folder with the name txt created. Now the file sample_success.txt can be used in tests to check if the above created controller function works as expected.

image.png

Contents of the sample_success.txt is set

some random content text
func TestConsumeFile_success(t *testing.T) {
    //Create multipart request
    body := new(bytes.Buffer)
    multipartWriter := multipart.NewWriter(body)
    //Create multipart header
    fileHeader := make(textproto.MIMEHeader)
    fileHeader.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, "file", "sample_success.csv"))
    fileHeader.Set("Content-Type", "text/plain")
    writer, err := multipartWriter.CreatePart(fileHeader)
    assert.Nil(t, err)
    //Copy file to file multipart writer
    file, err := os.Open("txt/sample_success.txt")
    assert.Nil(t, err)
    io.Copy(writer, file)
    // close the writer before making the request
    multipartWriter.Close()

    w := httptest.NewRecorder()
    ctx, r := gin.CreateTestContext(w)

    r.POST("/content/import", ConsumeFile)
    ctx.Request = httptest.NewRequest(http.MethodPost, "/content/import", body)
    ctx.Request.Header.Add("Content-Type", multipartWriter.FormDataContentType())
    r.ServeHTTP(w, ctx.Request)

    assert.Equal(t, http.StatusOK, w.Code)

}

Step 4

Now, it is time to run the main.go file to start the application and hit the endpoint http://localhost:8080/api/v1/contents/import

Example request using postman application.

image.png

If the endpoint is accessed via cUrl then the command would look something like this

curl --location --request POST 'http://localhost:8080/api/v1/contents/import' \
--form 'file=@"/filePathToTheProject/txt/sample_success.txt"'

Did you find this article valuable?

Support Deepak by becoming a sponsor. Any amount is appreciated!