Video:

Handling JSON

February 13, 2021

Course Instructor: Elliot Forbes

Hey Gophers! My name is Elliot and I'm the creator of TutorialEdge and I've been working with Go systems for roughly 5 years now. Feel free to reach out to me on twitter - @Elliot_F

Now, at present we don’t currently handle JSON and we don’t return JSON from our API, so it’s not exactly what you would call a JSON REST API just yet. So, in this video, we are going to look at how you can effectively work with JSON and encode and decode JSON within the transport package.

Implementation

So, let’s open up the handler.go file within the transport package. The first thing we are going to improve is the API health check endpoint and instead of returning a simple string, we are instead going to change this to return a JSON response.

Cool, so let’s navigate up to where we define our structs and define a new Response struct:

type Response struct {
    Message string
}

Cool, let’s navigate down to the healthcheck endpoint now, and let’s do a couple of things.

w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(Response{Message: "I am Alive"}); if err != nil {
    panic(err)
}

Cool, we save that and we should notice the encoding/json package has been automatically imported for us within the imports block.

Testing our Endpoint

Let’s navigate into Postman and hit the same health check endpoint with a GET request. As you can see, it has responded with the message “I am alive”.

We can also do one more thing within our health check endpoint and that is to set the Content-Type header:

w.Header().Set("Content-Type", "application/json; charset=UTF-8")

Let’s go back into Postman and hit the same endpoint and we should see the response is now nicely formatted in JSON.

Updating The Get Comment Endpoint

This is the approach we are going to be using for all of our existing comment endpoints.

Just at the start of the GetComment function, I want to do the same with these two lines:

w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)

And we also want to return the Comment object in JSON format. So, let’s update the code now and add:

if err := json.NewEncoder(w).Encode(comment); err != nil {
    panic(err)
}

Finally, we can remove the last fmt.Fprintf at the end of the function.

Testing Our Endpoint

Let’s navigate into Postman once again and try and hit this new endpoint. Try make a HTTP GET request to http://localhost:8080/api/comment/1 and we should see that it has successfully gone to the database and returned this comment object as an encoded JSON object.

Get Comments Endpoint

Let’s do the same for the GetComments endpoint which returns all of the comments from the database.

w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)

And we also want to return the slice of Comments in JSON format. So, let’s update the code now and add:

if err := json.NewEncoder(w).Encode(comments); err != nil {
    panic(err)
}

Perfect, let’s try hit this endpoint with a HTTP GET request and we should see that it has retrieved all of the comments from the database and returned them as a JSON array in the response body!

Updating the PostComment Endpoint

Note - I have updated the name of the comment here to be cmt so that it doesn’t overwrite the comment package in this PostComment function.

Now, let’s try and update the PostComment endpoint. Currently, it takes in a hardcoded Comment object and inserts that into the database. We want to be able to take in a JSON object from the HTTP request and unmarshal that into a Comment object before then attempting to save that into the database so let’s update this now:

var cmt comment.Comment
if err := json.NewDecoder(r.Body).Decode(&cmt); err != nil {
    fmt.Fprintf(w, "Failed to decodde JSON body")
}

Next, we want to remove the hardcoded comment and use the request that we have decoded from the request body:

cmt, err := h.Service.PostComment(cmt)
if err != nil {
    fmt.Fprintf(w, "Failed to post new comment")
}

Finally, we want to return the encoded comment:

if err := json.NewEncoder(w).Encode(cmt); err != nil {
    panic(err)
}

Let’s once again go into Postman and try this out with a HTTP POST request with the below JSON request body:

{ "slug": "/test-slug", "author": "TutorialEdge"}

You should see that it returns a Comment json object with things like the created and updated time, the slug we’ve just passed in and the author.

Updating Comments

Let’s make these changes to the UpdateComments handler function:

// UpdateComment - updates a comment by ID
func (h *Handler) UpdateComment(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusOK)
	var cmt comment.Comment
	if err := json.NewDecoder(r.Body).Decode(&cmt); err != nil {
		fmt.Fprintf(w, "Failed to decode JSON Body")
	}

	vars := mux.Vars(r)
	id := vars["id"]
	commentID, err := strconv.ParseUint(id, 10, 64)
	if err != nil {
		fmt.Fprintf(w, "Failed to parse uint from ID")
	}

	cmt, err = h.Service.UpdateComment(uint(commentID), cmt)
	if err != nil {
		fmt.Fprintf(w, "Failed to update comment")
	}
	if err := json.NewEncoder(w).Encode(cmt); err != nil {
		panic(err)
	}
}

We should now be able to read in an updated comment and then save those updates within the database. You can test this now in Postman with a HTTP PUT request.

Delete Comment

Finally, let’s update the DeleteComment handler so that it returns a JSON response upon successful deletion:

// DeleteComment - deletes a comment by ID
func (h *Handler) DeleteComment(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusOK)
	vars := mux.Vars(r)
	id := vars["id"]
	commentID, err := strconv.ParseUint(id, 10, 64)
	if err != nil {
		fmt.Fprintf(w, "Failed to parse uint from ID")
	}

	err = h.Service.DeleteComment(uint(commentID))
	if err != nil {
		fmt.Fprintf(w, "Failed to delete comment by comment ID")
	}

	if err := json.NewEncoder(w).Encode(Response{Status: "Successfully Deleted"}); err != nil {
		panic(err)
	}
}

Perfect, we have now successfully updated all of our endpoints to handle and return JSON!