Uploading Files in Golang with Multipart Request

13 Mar 2021
Uploading Files in Golang with Multipart Request

We live in the world of images and videos. In every application we have a use case where you want users to upload some images. But, how would you implement this in your applications?

In this article, I cover how to upload files (can be large as well) from your client to server as a multipart request. In future I’ll cover how to further push it to AWS S3 bucket using Golang.

I will be showcasing how to do this in Golang but the principle would be the same. I’ll divide the article into multiple sections - feel free to skip if you already know something. Let’s deep dive into the solution!

Introduction on Multipart Form Data

Whenever a POST request is created, one has to encode the data that forms the body of the request in some way. HTML forms provide three methods of encoding.

  • application/x-www-form-urlencoded (the default)
  • multipart/form-data
  • text/plain

There is a good Stackoverflow answer explaining this correctly. TLDR: When you are dealing with files you should use multipart/form-data

On the client side we have to specify this as enctype attribute value. For example -

<form action="upload-image" method="post" enctype="multipart/form-data">
  <div>
    <input id="profilePic" name="profilePic" type="file" />
    <input value="Upload" type="submit" />
  </div>
</form>

So multipart request is a REST request that consists of several packed REST requests inside its entity. So, when you upload the image other data like Filename, headers like file type are passed as well.


Parsing Multipart Form in Golang

Now that we understand what multipart form data is let’s focus on server side to parse this data. I’ll cover the client side later in the articles which show how to create requests via Postman.

The code below contains a HandlePost function that parses Multipart Form data using ParseMultipartForm() and FormFile() methods.

func HandlePost(w http.ResponseWriter, r *http.Request) {

   // Parse request body as multipart form data with 32MB max memory
   err = r.ParseMultipartForm(32 << 20)
   if err != nil {
       logger.Error(err)
   }

   // Get file uploaded via Form
   file, handler, err := r.FormFile("file")
   if err != nil {
       logger.Error(err)
       SendErrorResponse(w, http.StatusInternalServerError, ".errors.upload_image.cannot_read_file")
       return
   }
   defer file.Close()
}
  • ParseMultipartForm() - This is required for uploading files to server memory. We define maximum memory as 32MB. Anything greater than that will be stored in a temporary file on the system.
  • FormFile() - This is used to get the File Metadata like Headers, file size etc. This method returns the first file for the provided form key which we can use to save file locally on server.

Storing the uploaded file on Server

Ok, you must be thinking. I got the file data but I still don’t see the file I uploaded on the server. Let’s do that before we move forward to uploading it on S3.

// Create file locally
localFile, err := os.Create(handler.Filename)
if err != nil {
   logger.Error(err)
   SendErrorResponse(w, http.StatusInternalServerError, ".errors.upload_image.cannot_create_local_file")
   return
}
defer dst.Close()

// Copy the uploaded file data to the newly created file on the filesystem
if _, err := io.Copy(localFile, file); err != nil {
   logger.Error(err)
   SendErrorResponse(w, http.StatusInternalServerError, ".errors.upload_image.cannot_copy_to_file")
   return
}

The code is pretty straightforward. We create a new file using os.Create() and then copy the content of the uploaded file to this new file using io.Copy().

To verify this is working. You need to create a POST request via client (say Postman) and set Content-Type Header as multipart/form-data , finally upload image in body as form-data

Postman Multipart Request

Awesome, now you should see a new file created in your root folder of source code. Tada!

Here’s the complete POST handler function.

func (client *ImageUploadAPIClient) HandlePost(w http.ResponseWriter, r *http.Request) {

   // Parse request body as multipart form data with 32MB max memory
   err = r.ParseMultipartForm(32 << 20)
   if err != nil {
       logger.Error(err)
   }

   // Get file from Form
   file, handler, err := r.FormFile("file")
   if err != nil {
       logger.Error(err)
       SendErrorResponse(w, http.StatusInternalServerError, ".errors.upload_image.cannot_read_file")
       return
   }
   defer file.Close()

   // Create file locally
   dst, err := os.Create(handler.Filename)
   if err != nil {
       logger.Error(err)
       SendErrorResponse(w, http.StatusInternalServerError, ".errors.upload_image.cannot_create_local_file")
       return
   }
   defer dst.Close()

   // Copy the uploaded file data to the newly created file on the filesystem
   if _, err := io.Copy(dst, file); err != nil {
       logger.Error(err)
       SendErrorResponse(w, http.StatusInternalServerError, ".errors.upload_image.cannot_copy_to_file")
       return
   }
   w.WriteHeader(http.StatusOK)
}

Resources

Liked the article? Consider supporting me ☕️

Books to learn Golang


I hope you learned something new. Feel free to suggest improvements ✔️

I share regular updates and resources on Twitter. Let’s connect!

Keep exploring 🔎 Keep learning 🚀

Liked the content? Do support :)

Paypal - Mohit Khare
Buy me a coffee