Rest api to upload images in Go
Tags: go, api, tutorial, webdev
Published: 2022-11-29 11:38:53



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.
```go
$ mkdir upload-images-rest-api
```
### 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
$ go mod init upload-images-rest-api
```
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`
```go
$ touch main.go
```
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](https://github.com/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
$ go get -u github.com/gorilla/mux 
```

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
```go
package main

func main() {

}
```

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.
```go
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))
}
```
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 `/ping`is 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.

```go
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

```

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:
```go
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)
}
```
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 `Ping `function let's add this in our `/ping` handler.

```go
r.HandleFunc("/ping", Ping).Methods("GET")
```
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fpcfm9unadp9yj61vez6.png)

**Browser**
![browser](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pv56m7o9mloqw948c79o.png)

`/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
```go
// 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)
}
```
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.

```go
r.HandleFunc("/upload", UploadImages).Methods("POST")
```

### 6. Test the API.
We have connected our handlers with our routers, It's time to test the `/upload` route:

![upload image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m1iev1wrxcoyj3alyf9f.png)

![file-uploaded](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/el5n64898w7rzi70r74e.png)

_This article is originally posted on [programmingeeksclub.com](https://programmingeeksclub.com/rest-api-to-upload-image-in-golang/)_

_My Personal Blogging Website : [Programming Geeks Club](https://programmingeeksclub.com/)_
_My Facebook Page : [Programming Geeks Club](https://www.facebook.com/profile.php?id=100086258693659)_
_My Telegram Channel : [Programming Geeks Club](https://t.me/dpgcl)_
_My Twitter Account : [Kuldeep Singh](https://twitter.com/kusinghofficial)_
_My Youtube Channel: [Programming Geeks Club](https://youtube.com/@programmingeeksclub)_