RESTful Web Service in Golang and Front-end Served with VueJS

Continue from the previous topic

There is one behaviour in our Golang web application that will frustrate our users. Everytime we add, update, or delete a video record, the web page gets refreshed. So if at that time there is a video being played, then poof, it’s gone after you add, update, or delete a record. That’s a bad UX.

To overcome there, I decide to implement RESTful web services in the Golang project and the frontend will use VueJS library to update the web page.

Firstly, we need to wrap a web service interface over the CRUD functions we have in our web application. JSON will be used as the data transport format. To do that, we will introduce a new handler function to multiplex request to the correct function in our RESTful web service.

func handleVideoAPIRequests(video models.IVideo) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {

var err error
switch request.Method {
case "GET":
err = handleVideoAPIGet(writer, request, video)

case "POST":
err = handleVideoAPIPost(writer, request, video)

case "PUT":
err = handleVideoAPIPut(writer, request, video)

case "DELETE":
err = handleVideoAPIDelete(writer, request, video)
}

if err != nil {
util.CheckError(err)

return
}

}
}

HTTP GET

Then for each of the HTTP methods, we will process the request independently. For example, to retrieve the list of videos or one of the videos, we will use GET method, i.e. handleVideoAPIGet().

The list of video records at the right are retrieved using HTTP GET.
func handleVideoAPIGet(writer http.ResponseWriter, request *http.Request, video models.IVideo) (err error) {

videoIDURL := path.Base(request.URL.Path)

var output []byte

if videoIDURL == "video" {
videos, errIf := video.GetAllVideos()
err = errIf
util.CheckError(errIf)

output, errIf = json.MarshalIndent(&videos, "", "\t")
err = errIf
util.CheckError(errIf)

writer.Header().Set("Content-Type", "application/json")
writer.Write(output)

return
}

videoID, err := strconv.Atoi(videoIDURL)

if err != nil {
util.CheckError(err)
return
}

err = video.GetVideo(videoID)
util.CheckError(err)

output, err = json.MarshalIndent(&video, "", "\t")
util.CheckError(err)

writer.Header().Set("Content-Type", "application/json")
writer.Write(output)

return
}

This method seems long but in the first half of the function, it is checking if the URL ends with /video or it ends with a number. If it ends with /video, that means it is not asking for a specific video but it’s asking for all valid videos. Thus video.GetAllVideos() is called and the videos are returned as JSON array.

So what if it’s only requesting for particular video with video ID? There is where the second half of the function comes in. It will first convert the part of the URL into an integer using strconv.Atoi(). Then we will retrieve the video based on that integer as video ID and then return it as a JSON object.

HTTP POST

To create a new video record in database, the system will call the handleVideoAPIPost().

func handleVideoAPIPost(writer http.ResponseWriter, request *http.Request, video models.IVideo) (err error) {

length := request.ContentLength
body := make([]byte, length)
request.Body.Read(body)
json.Unmarshal(body, &video)

err = video.CreateVideo()
if err != nil {
util.CheckError(err)
apiStatus := models.APIStatus{
Status: false,
Message: err.Error(),
}

output, err := json.MarshalIndent(&apiStatus, "", "\t")
util.CheckError(err)

writer.WriteHeader(400)
writer.Header().Set("Content-Type", "application/json")

writer.Write(output)
} else {
apiStatus := models.APIStatus{
Status: true,
Message: "A video is successfully added to the database.",
}

output, err := json.MarshalIndent(&apiStatus, "", "\t")
util.CheckError(err)

writer.WriteHeader(200)
writer.Header().Set("Content-Type", "application/json")
writer.Write(output)
}

return
}

The beginning of the function for POST method is reading from body is because we will post a JSON object containing the new video record information to it. So it needs to retrieve the JSON object from the body.

Another interesting thing in this function is that it will return JSON object indicating whether the action of adding new record is successful or not with a corresponding HTTP status code of 400 or 200. Doing so is to let the frontend feedback to the user so that the user knows whether the record is successfully inserted to the database or not.

HTTP PUT

How about if we want to update existing video record? Well, we can rely on the PUT method. The PUT method requires us to tell it which resource it will update. Hence the function handleVideoAPIPut() is as follows.

func handleVideoAPIPut(writer http.ResponseWriter, request *http.Request, video models.IVideo) (err error) {

videoIDURL := path.Base(request.URL.Path)
videoID, err := strconv.Atoi(videoIDURL)
if err != nil {
util.CheckError(err)
return
}

err = video.GetVideo(videoID)
if err != nil {
util.CheckError(err)
apiStatus := models.APIStatus{
Status: false,
Message: err.Error(),
}

output, err := json.MarshalIndent(&apiStatus, "", "\t")
util.CheckError(err)

writer.WriteHeader(400)
writer.Header().Set("Content-Type", "application/json")
writer.Write(output)
}

length := request.ContentLength
body := make([]byte, length)
request.Body.Read(body)
json.Unmarshal(body, &video)

err = video.UpdateVideo()
if err != nil {
util.CheckError(err)
apiStatus := models.APIStatus{
Status: false,
Message: err.Error(),
}

output, err := json.MarshalIndent(&apiStatus, "", "\t")
util.CheckError(err)

writer.WriteHeader(400)
writer.Header().Set("Content-Type", "application/json")
writer.Write(output)
} else {
apiStatus := models.APIStatus{
Status: true,
Message: "A video record is successfully updated.",
}

output, err := json.MarshalIndent(&apiStatus, "", "\t")
util.CheckError(err)

writer.WriteHeader(200)
writer.Header().Set("Content-Type", "application/json")
writer.Write(output)
}

return
}

Similar to how we have done in handleVideoAPIGet(), we first need to get the video ID from the URL with the help of strconv.Atoi(). Then we will check whether there is an existing video in the database with the video ID. If there is none, then we simply return JSON object updating frontend with an error message. If there is video found with the video ID, we will then proceed to update it with the info from the JSON object passed via the request body.

There is one thing to take note here is that we are not replacing the existing video record entirely. We are only updating part of it. So the JSON object should contain only the fields needed to be updated.

Updating the video record.

HTTP DELETE

The function to handle DELETE method will be similar to the one handling PUT method.

func handleVideoAPIDelete(writer http.ResponseWriter, request *http.Request, video models.IVideo) (err error) {

   videoIDURL := path.Base(request.URL.Path)
    videoID, err := strconv.Atoi(videoIDURL)

    if err != nil {
        util.CheckError(err)
        return
    }

   err = video.GetVideo(videoID)
   if err != nil {
        util.CheckError(err)

        apiStatus := models.APIStatus{
            Status: false,
            Message: err.Error(),
        }
        output, err := json.MarshalIndent(&apiStatus, "", "\t")
        util.CheckError(err)

        writer.WriteHeader(400)
        writer.Header().Set("Content-Type", "application/json")
        writer.Write(output)

    }

    err = video.DeleteVideo()
    if err != nil {

        util.CheckError(err)

        apiStatus := models.APIStatus{
            Status: false,
            Message: err.Error(),
        }
        output, err := json.MarshalIndent(&apiStatus, "", "\t")
        util.CheckError(err)

        writer.WriteHeader(400)
        writer.Header().Set("Content-Type", "application/json")
        writer.Write(output)

    } else {

        apiStatus := models.APIStatus{
            Status: true,
            Message: "A video record is deleted.",
        }
        output, err := json.MarshalIndent(&apiStatus, "", "\t")
        util.CheckError(err)

        writer.WriteHeader(200)
        writer.Header().Set("Content-Type", "application/json")
        writer.Write(output)

    }

    return
}

Similar to how we have done in handleVideoAPIPut(), we first need to get the video ID from the URL with the help of strconv.Atoi(). Then we will check whether there is an existing video in the database with the video ID. If there is none, then we simply return JSON object updating frontend with an error message. If there is video found with the video ID, we will then proceed to delete it.

The popup to check if user really wants the video record to be removed from the list.

Frontend with VueJS

Now, let’s see how we use VueJS library to display the video list.

It is done with just a for loop to list down all the relevant videos and having values stored in data attributes for update and delete the video record.

References