DEV Community

Cover image for Rest api to upload images in Go
Kuldeep Singh
Kuldeep Singh

Posted on • Originally published at programmingeeksclub.com

Rest api to upload images in Go

In this article we are going to learn about how we can create a rest api to upload image, we'll also going to make sure that our api will handle multiple image upload as well and we are also going to set the file upload limit so it won't make our system unstable, because long file upload can cause the system failure issue so it's better to add a limit for the file, and we are going to make sure that we only accept image so we also need to add file type checking as well for our api, if you want images and docs from the same api you can remove the type checking filter and api is ready to handle multiple type files.

Steps we need to follow to make our rest api to upload images.

  • Create Repostory.
  • Setup go.mod.
  • Create main.go file
  • Setup http server using gorilla mux package.
  • Create the api.
  • Connect the rest api with http handler.
  • Test the api.

Let's follow the steps one by one to create the successfull rest api to upload images.

1. Create Repository

First we need to create the folder where we can setup our codebase.
Create a folder called upload-images-rest-api. below is the command to create directory/folder.

$ mkdir upload-images-rest-api
Enter fullscreen mode Exit fullscreen mode

2. Setup go.mod

We also need to setup go.mod file in upload-images-rest-api folder, so we can use our code as package/module, below is the command to generate the go.mod file.

$ go mod init upload-images-rest-api
Enter fullscreen mode Exit fullscreen mode

If you are new to Go then you might need to understand the go packaging first to understand what this command do.

3. Create main.go file

Inside your upload-images-rest-api folder create another file called main.go

$ touch main.go
Enter fullscreen mode Exit fullscreen mode

as now we have also created our main.go file now we can start development of our http server.

4. Setup http server using gorilla mux package

For creating server we are going to use gorilla mux package so first we need to download the package first then we can start the development.

Below is the command to download the gorilla mux package

$ go get -u github.com/gorilla/mux 
Enter fullscreen mode Exit fullscreen mode

After the installation of gorilla mux we can start the development of our code.

1. Create main function.

First we need to write the boiler plate code of Go, below is the code

package main

func main() {

}
Enter fullscreen mode Exit fullscreen mode

As of now it's a boiler plate code that's why we don't have any package loaded yet.

2. Create HTTP Server.

Below is the code of the http server.

package main

import (
    "log"
    "net/http"

    "github.com/gorilla/mux"
)

const PORT = "8080"

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/ping", nil).Methods("GET")
    r.HandleFunc("/upload", nil).Methods("POST")

    log.Printf("Server is running on http://localhost:%s", PORT)
    log.Println(http.ListenAndServe(":"+PORT, r))
}
Enter fullscreen mode Exit fullscreen mode

First we added the PORT variable in which we want our server to run which is 8080, Inside the main function we have called the mux.NewRouter function of our gorilla mux package and then we have created our routes as mentioned in the code and also attached their methods as well, as you can see we have two API, first /pingis to check if our server is alive or not and second /upload is for our main work to upload images, then we're passing our mux router to http.ListenAndServe function and also passing the port on we want our server to run.

If you try to run the server and go to test any of the api you'll get the error like this because we just passed the path but for the provided path their is no handler which can read that handle that request.

2022/11/27 19:16:00 http: panic serving [::1]:56663: runtime error: invalid memory address or nil pointer dereference
goroutine 18 [running]:
net/http.(*conn).serve.func1()
        C:/Program Files/Go/src/net/http/server.go:1850 +0xbf
panic({0xe08000, 0x1025de0})
        C:/Program Files/Go/src/runtime/panic.go:890 +0x262
net/http.HandlerFunc.ServeHTTP(0xc000148000?, {0xebd248?, 0xc0001360e0?}, 0x800?)
        C:/Program Files/Go/src/net/http/server.go:2109 +0x1e
github.com/gorilla/mux.(*Router).ServeHTTP(0xc000130000, {0xebd248, 0xc0001360e0}, 0xc000096100)
        C:/Users/KDSINGH/go/pkg/mod/github.com/gorilla/mux@v1.8.0/mux.go:210 +0x1cf
net/http.serverHandler.ServeHTTP({0xc000088120?}, {0xebd248, 0xc0001360e0}, 0xc000096100)
        C:/Program Files/Go/src/net/http/server.go:2947 +0x30c
net/http.(*conn).serve(0xc00009e000, {0xebd680, 0xc000073320})
        C:/Program Files/Go/src/net/http/server.go:1991 +0x607
created by net/http.(*Server).Serve
        C:/Program Files/Go/src/net/http/server.go:3102 +0x4db

Enter fullscreen mode Exit fullscreen mode

To solve this issue let's create our a http handler for our /ping api which will be used as a api heartbeat to check if server is running or not.

Let's add the handler for the /ping route:

func Ping(w http.ResponseWriter, r *http.Request) {
    answer := map[string]interface{}{
        "messageType": "S",
        "message":     "",
        "data":        "PONG",
    }
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(200)
    json.NewEncoder(w).Encode(answer)
}
Enter fullscreen mode Exit fullscreen mode

We have added the Ping function to handle the /ping route and inside the Ping function we have added our response structure as map[string]interface so we can add dynamic response as we want, we're not dependent on struct. We have added messageType, message and data as our response we going to use the same response json for our /upload api expect that data will be going to a struct with multiple fields.

In next we're write our header content type and http code and then encoding and directly returning back our response as json.

3. Add Ping function into handler

Now we have our Pingfunction let's add this in our /ping handler.

r.HandleFunc("/ping", Ping).Methods("GET")
Enter fullscreen mode Exit fullscreen mode

Now we have added our Ping handler as well, Let's test the API.
You can test the api using postman or your browser as ping doesn't accepts any parameter so it can be test on browser as well.

Postman
postman

Browser
browser

/ping is working as expected, now it's time to implement our main handler as well.

4. Create the api to upload images

Below is the full code of upload image rest api

// handler to handle the image upload
func UploadImages(w http.ResponseWriter, r *http.Request) {
    // 32 MB is the default used by FormFile() function
    if err := r.ParseMultipartForm(BULK_FILE_SIZE); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // Get a reference to the fileHeaders.
    // They are accessible only after ParseMultipartForm is called
    files := r.MultipartForm.File["file"]

    var errNew string
    var http_status int

    for _, fileHeader := range files {
        // Open the file
        file, err := fileHeader.Open()
        if err != nil {
            errNew = err.Error()
            http_status = http.StatusInternalServerError
            break
        }

        defer file.Close()

        buff := make([]byte, 512)
        _, err = file.Read(buff)
        if err != nil {
            errNew = err.Error()
            http_status = http.StatusInternalServerError
            break
        }

        // checking the content type
        // so we don't allow files other than images
        filetype := http.DetectContentType(buff)
        if filetype != "image/jpeg" && filetype != "image/png" && filetype != "image/jpg" {
            errNew = "The provided file format is not allowed. Please upload a JPEG,JPG or PNG image"
            http_status = http.StatusBadRequest
            break
        }

        _, err = file.Seek(0, io.SeekStart)
        if err != nil {
            errNew = err.Error()
            http_status = http.StatusInternalServerError
            break
        }

        err = os.MkdirAll("./uploads", os.ModePerm)
        if err != nil {
            errNew = err.Error()
            http_status = http.StatusInternalServerError
            break
        }

        f, err := os.Create(fmt.Sprintf("./uploads/%d%s", time.Now().UnixNano(), filepath.Ext(fileHeader.Filename)))
        if err != nil {
            errNew = err.Error()
            http_status = http.StatusBadRequest
            break
        }

        defer f.Close()

        _, err = io.Copy(f, file)
        if err != nil {
            errNew = err.Error()
            http_status = http.StatusBadRequest
            break
        }
    }
    message := "file uploaded successfully"
    messageType := "S"

    if errNew != "" {
        message = errNew
        messageType = "E"
    }

    if http_status == 0 {
        http_status = http.StatusOK
    }

    resp := map[string]interface{}{
        "messageType": messageType,
        "message":     message,
    }
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http_status)
    json.NewEncoder(w).Encode(resp)
}
Enter fullscreen mode Exit fullscreen mode

We have added our new handler UploadImages and added the check for limited size data upload for our /upload endpoint, we're passing the BULK_FILE_SIZE into r.ParseMultipartForm function.

In next step we're getting the all uploaded file using r.MultipartForm.File["file"] and it's giving us the map[string][]*multipart.FileHeader on which we're iterating our loop sequentially.

Inside the loop first we're opening the file using fileHeader.Open() and processing the returned information file, next we're reading the opened file information in chunks buff := make([]byte, 512) using file.Read(buff) function: we're passing our opened file to Read method and also passing the bytes we need to read from the opened file.

After reading small chunk from file we're passing that chunks http.DetectContentType function and it's returning back the file type and in next step we're checking file type, we're only accepting JPEG, JPG and PNG images.

In next step we're calling file.Seek(0, io.SeekStart) for seeking image data fron given offset to whence, then we're creating the uploads folder in the root level of the project, after creating folder we're creating the file where we can save the image data we have opened, and in next we're calling io.Copy(f, file) and passing the data file into our newly created file f.

In the end of the function we're just processing the request and we need if the function got any error processing the image then it'll return the error otherwise it'll return back the successfull message as type json response.

5. Connect the rest api with http handler

Now we have our handler for /upload api but it's not connected yet so let's connect it and then we'll test our code, We just need to add the UploadImages function as second argument into our /upload handlefunc.

r.HandleFunc("/upload", UploadImages).Methods("POST")
Enter fullscreen mode Exit fullscreen mode

6. Test the API.

We have connected our handlers with our routers, It's time to test the /upload route:

upload image

file-uploaded

This article is originally posted on programmingeeksclub.com

My Personal Blogging Website : Programming Geeks Club
My Facebook Page : Programming Geeks Club
My Telegram Channel : Programming Geeks Club
My Twitter Account : Kuldeep Singh
My Youtube Channel: Programming Geeks Club

Top comments (0)