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
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
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
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
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() {
}
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
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.
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:
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.
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.
/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 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")
6. Test the API.
We have connected our handlers with our routers, It's time to test the /upload
route:
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)