Golang file uploading

Golang file uploading

File uploading is an essential part of many great applications of our time but can be challenging to implement in our own applications.

In this article, you will build a simple file uploading HTTP server in Golang that allows you to upload files to the server running the application. You will also containerize the application using Docker.

Creating the project

Before we can start coding, you first need to create the files needed for the project. You can use the following commands for that.

mkdir public
touch main.go Dockerfile public/upload.html

Basic HTTP server

After completing the setup of the folder structure, you will continue by implementing a basic HTTP server using the net/http package.

The server will feature a single endpoint with a GET and POST request implementation. The GET request should display the frontend that we will create in a later section. A POST request, on the other hand, should trigger the file uploading process.

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"text/template"
)

// Compile templates on start of the application
var templates = template.Must(template.ParseFiles("public/upload.html"))

// Display the named template
func display(w http.ResponseWriter, page string, data interface{}) {
	templates.ExecuteTemplate(w, page+".html", data)
}

func uploadHandler(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "GET":
		display(w, "upload", nil)
	case "POST":
		uploadFile(w, r)
	}
}

func main() {
	// Upload route
	http.HandleFunc("/upload", uploadHandler)

	//Listen on port 8080
	http.ListenAndServe(":8080", nil)
}

As you can see, the uploadHandler() function checks if the request is of type GET or POST and then directs it to the right method.

Implementing the file uploading

Now that you have a basic HTTP server to build on let's continue by implmenting the file uploading functionality. We can do that by getting the file from the POST request we received.

func uploadFile(w http.ResponseWriter, r *http.Request) {
	// Maximum upload of 10 MB files
	r.ParseMultipartForm(10 << 20)

	// Get handler for filename, size and headers
	file, handler, err := r.FormFile("myFile")
	if err != nil {
		fmt.Println("Error Retrieving the File")
		fmt.Println(err)
		return
	}

	defer file.Close()
	fmt.Printf("Uploaded File: %+v\n", handler.Filename)
	fmt.Printf("File Size: %+v\n", handler.Size)
	fmt.Printf("MIME Header: %+v\n", handler.Header)
}

As you can see, we use the FromFile() function on our request object to retrieve the file and handler. After that, we can read the file data, as shown in the fmt.Printf() statements.

The next step is saving the retrieved file on our local filesystem. For that, we first need to create a file and then copy the retrieved file to the created file on the filesystem.

func uploadFile(w http.ResponseWriter, r *http.Request) {
	...
	// Create file
	dst, err := os.Create(handler.Filename)
	defer dst.Close()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// Copy the uploaded file to the created file on the filesystem
	if _, err := io.Copy(dst, file); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	fmt.Fprintf(w, "Successfully Uploaded File\n")
}

This code leaves us with the following main.go file.

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"text/template"
)

// Compile templates on start of the application
var templates = template.Must(template.ParseFiles("public/upload.html"))

// Display the named template
func display(w http.ResponseWriter, page string, data interface{}) {
	templates.ExecuteTemplate(w, page+".html", data)
}

func uploadFile(w http.ResponseWriter, r *http.Request) {
	// Maximum upload of 10 MB files
	r.ParseMultipartForm(10 << 20)

	// Get handler for filename, size and headers
	file, handler, err := r.FormFile("myFile")
	if err != nil {
		fmt.Println("Error Retrieving the File")
		fmt.Println(err)
		return
	}

	defer file.Close()
	fmt.Printf("Uploaded File: %+v\n", handler.Filename)
	fmt.Printf("File Size: %+v\n", handler.Size)
	fmt.Printf("MIME Header: %+v\n", handler.Header)

	// Create file
	dst, err := os.Create(handler.Filename)
	defer dst.Close()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// Copy the uploaded file to the created file on the filesystem
	if _, err := io.Copy(dst, file); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	fmt.Fprintf(w, "Successfully Uploaded File\n")
}

func uploadHandler(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "GET":
		display(w, "upload", nil)
	case "POST":
		uploadFile(w, r)
	}
}

func main() {
	// Upload route
	http.HandleFunc("/upload", uploadHandler)

	//Listen on port 8080
	http.ListenAndServe(":8080", nil)
}

Frontend

Now that the backend is ready, we need a simple frontend to act as a portal for uploading our files. For that, we will create a simple multipart form with and file input and a submit button.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Upload File</title>
  </head>
  <body>
    <form
      enctype="multipart/form-data"
      action="http://localhost:8080/upload"
      method="post"
    >
      <input type="file" name="myFile" />
      <input type="submit" value="upload" />
    </form>
  </body>
</html>

The frontend will automatically be served on the /upload endpoint when running the application.

Testing the application

Awesome, now that we have finished the application, you can run it using the following command.

go run main.go

Go to localhost:8080/upload, and you will see a form to upload a file. After selecting a file and clicking upload, the file should be created in your local filesystem.

Containerizing the application

The last step now is to containerize the application using Docker. For that, we can use a simple Dockerfile that builds and runs the application.

FROM golang:latest

COPY . . 

# Build the application
RUN go build -o main .

# Expose port 8080 to the outside world
EXPOSE 8080

# Command to run the executable
CMD ["./main"]

Build the image can be done using the build command. The -t flag is used to give the image a custom tag.

docker build -t fileuploading .

You can now run the image using the run command. The -p flag is used to set the port that should be exposed to the host machine.

docker run -p 8080:8080 fileuploading

The whole project and many other projects for learning Golang can also be found on my Github.

Conclusion

You made it all the way until the end! I hope that this article helped you understand file uploading in Go and how you can use it in your applications.

If you have found this useful, please consider recommending and sharing it with other fellow developers and subscribing to my newsletter. If you have any questions or feedback, let me know using my contact form or contact me on twitter.

Read these next: