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 a Repository.
- 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 successful rest api to upload images.
1. Create a Repository for our rest api
First we need to create the folder where we can set up our codebase.
Create a folder called upload-images-rest-api
. Below is the command to create a directory/folder.
$ mkdir upload-images-rest-api
2. Setup go.mod
We also need to setup the go.mod file in the upload-images-rest-api
folder, so we can use our code as a package/module, below is the command to generate the go.mod file.
$ 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 does.
3. Create main.go file
Inside your upload-images-rest-api
folder create another file called main.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 the server we are going to use the 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
After the installation of gorilla mux we can start developing the code.
1. Create a main function.
First we need to write the boiler plate code of Go, below is the code.
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.
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, and it is to check if our server is alive or not and second /upload and it 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 there 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
To solve this issue let’s create a http handler for our /ping api which will be used as an api heartbeat to check if the 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) }
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.
Next we 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.
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 accept any parameter so it can be test on the browser as well.
Postman:
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) }
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 a chunk from a 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 from 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 the 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 successful 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 a second argument into our /upload handlefunc.
r.HandleFunc(“/upload”, UploadImages).Methods(“POST”)
6. Test the Rest API.
We have connected our handlers with our routers, It’s time to test the /upload route:
your comments are appreciated and if you wants to see your articles on this platform then please shoot a mail at this address kusingh@programmingeeksclub.com
Thanks for reading