Video:

Handling Errors

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.

Currently, within our API, whenever the comment service responds with an error within our http handler functions it will just return a string back to the client calling this endpoint.

As it stands, this is horrible if you are the frontend developer trying to interface with this API endpoint as it gives absolutely no helpful clues as to whether or not the call to this API endpoint has failed.

In this video, we are going to improve the way our application handles internal errors and the way that it sends these errors back to any client applications.

Helper Functions

Let’s start off by creating a new helper function that will encapsulate all of the logic for sending an appropriate http status code back to the client as well as the error message:

func sendErrorResponse(w http.ResponseWriter, message string, err error) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusInternalServerError)
	if err := json.NewEncoder(w).Encode(Response{Message: message, Error: err.Error()}); err != nil {
		panic(err)
	}
}

Next, let’s update our handler functions to use this helper function. We need to ensure that we remove the calls to set the header and to write the status code from the top of every handler function as this will now be done by our helper functions:

Note - For brevity, I’m only going to update the delete comment handler function in this article. I’m leaving the updates to the viewer as an exercise.

// DeleteComment - deletes a comment by ID
func (h *Handler) DeleteComment(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id := vars["id"]
	commentID, err := strconv.ParseUint(id, 10, 64)
	if err != nil {
		sendErrorResponse(w, "Failed to parse uint from ID", err)
        // Don't forget to return!
		return
	}

	err = h.Service.DeleteComment(uint(commentID))
	if err != nil {
		sendErrorResponse(w, "Failed to delete comment by comment ID", err)
        // Don't forget to return!
		return
	}

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

Perfect, we now have our errors sending back the appropriate status code and an error message that the downstream clients can work with.

Success Helper Function

Next, we’ll need to define a success helper function that will handle sending the OK status code and the appropriate response when our service has worked as expected.

func sendOkResponse(w http.ResponseWriter, resp interface{}) error {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusOK)
	return json.NewEncoder(w).Encode(resp)
}

Let’s now update each of our handler functions to use this new helper function:

Note - again, for brevity, I’m only going to show the delete comment handler function:

// DeleteComment - deletes a comment by ID
func (h *Handler) DeleteComment(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id := vars["id"]
	commentID, err := strconv.ParseUint(id, 10, 64)
	if err != nil {
		sendErrorResponse(w, "Failed to parse uint from ID", err)
		return
	}

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

	if err = sendOkResponse(w, Response{Message: "Successfully Deleted"}); err != nil {
		panic(err)
	}
}

Conclusion

Perfect, in this video we have considerably improved the way that our REST API handles and sends back responses to the client’s calling our API. We now send a more appropriate HTTP status code if there is an error and we send back a nice JSON response for our clients to easily use.