Go With A Vue

Chris Berry Go, Vue 1 Comment

Last year I blogged about creating a Lean Mean Vue Machine called Quotes on Demand. The application was a fully-featured CRUD application served from a NodeJS server and had a self-contained VueJS front end. Since then I’ve also added a Python version of the same API.

But wouldn’t it be a nice test to see if that same Vue application could switch over to another API – say, something like a Golang application server?

In this post, we will create a Go application server that will have 100% parity to an existing NodeJS web application. This will enable an existing VueJS front end to connect to the application with no additional code changes in the user interface code.

What is Go?

Golang, or simply Go, is a C-style language created by Google back in 2003. It’s a little simpler than that, though. The language is typed (meaning it has integers and strings), but it’s also a terse language with Structs and For Loops used exclusively instead of Classes and ForEach Loops. The language sounded challenging, so I decided to take it for a spin.

Application Goals

This application was created with these three goals in mind:

  1. To create a Go application server that has 100% parity of the original NodeJS server. All CRUD functionality would be the same using the same JSON data file as the original.
  2. To enable the VueJS application from Quotes on Demand to “just work” with the Go server. Basically, all front end functionality would be exactly the same with no changes to the existing application logic.
  3. To continue to allow the VueJS application stand alone outside of the Gong server.

Application Structure

The application structure is actually quite simple; it’s broken into two distinct sections: the UI and the API. Since we are building a new API for this application, we will focus on that section. For more information about the UI, please reference the Lean Mean Vue Machine blog entry.

The API folder is a fully self-contained Go application server that will load a selection of quotes up into an in-memory collection that will allow connecting parties to do the five main CRUD actives such as:

1 /quote           | GET    | returns all quotes
2 /quote/{id}      | GET    | returns a specific quote by 'id'
3 /quote           | POST   | adds a new quote
4 /quote/{id}      | PUT    | updates a quote
5 /quote/{id}      | DELETE | deletes a quote

This server is CORS enabled, so all endpoints are open to public consumption, and there is no security on the endpoints for simplicity. The quotes API itself does support proper HTTP verb endpoints.

Application Building Process

Our application needed to have Go libraries installed to make the API server development easier to work with.

github.com/gorilla/handlers // used for CORS
github.com/gorilla/mux // used for routing

The first file created was named main.go. Notice that all of the Go files are suffixed with the dot go extension to designate them as a Go file.

api/main.go

package main

import (
	"fmt"
	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"log"
	"net/http"
	"quotes-on-demand-go/library"
)

func HealthCheck(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "API is alive and ready")
}

func main() {

	library.LoadData()

	router := mux.NewRouter()
	router.HandleFunc("/quote/", library.GetQuote).Methods("GET")
	router.HandleFunc("/quote/{id}", library.GetQuoteById).Methods("GET")
	router.HandleFunc("/quote/", library.CreateQuote).Methods("POST")
	router.HandleFunc("/quote/", library.UpdateQuote).Methods("PUT")
	router.HandleFunc("/quote/{id}", library.DeleteQuote).Methods("DELETE")
	router.HandleFunc("/", HealthCheck).Methods("GET")

	var port = ":5000"
	print("Listening And Serving on " + port)
	log.Fatal(http.ListenAndServe(port, handlers.CORS(
		handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Authorization"}),
		handlers.AllowedMethods([]string{"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS"}),
		handlers.AllowedOrigins([]string{"*"}))(router)))

}

Code Explanations

From top to bottom, I’ll explain the code.

First, we import mux and handlers from our included dependencies, along with several other standard library items. Then, we import a second Go package file, which was created to hold the actual CRUD functionality.

We then create a simple HealthCheck function for the root route of the application so that later, we can easily see if the server is up and running.

Next, we create our main function, which is the actual entry point to the application. Inside the main function, six routes have been created. Five are CRUD routes: (1) a GET all function, (2) a GET BY ID function, (3) a POST for creating a new quote, (4) a PUT for updating a quote and a delete for removing a quote from the collection, and (5) a root route function (created using our health check function from earlier) to notify the calling applications that the API is up and running.

See Also:  Lean Mean Vue Machine

Notice, in our five CRUD routes, only functions from our imported library package are referenced. This separation makes our actual routes much cleaner and easier to read.

Lastly, we have our log.Fatal function which accepts our startup for the HTTP server on a given port, along with setting up the handlers from our imported package from earlier. Handlers\ is used here to set headers, origins, and methods for Cross Origin Requests (CORS).

Now, let’s take a look at the functionality held within crud.go.

api/library/crud.go

package library

import (
	"encoding/json"
	"fmt"
	"github.com/gorilla/mux"
	"io/ioutil"
	"net/http"
	"strconv"
)

// this is our data structure
type Quote struct {
	Id     int    `json:"id"`
	Author string `json:"author"`
	Text   string `json:"text"`
}

// our `splice` collection for the filled Quotes
var data []Quote

// this is a private function to this file because it is not Capitalized
func findHighestId() int {
	maxId := data[0].Id
	for _, v := range data {
		if v.Id > maxId {
			maxId = v.Id
		}
	}
	return maxId
}

// load the JSON data file for usage.
func LoadData() {
	var content, err = ioutil.ReadFile("data.json")
	if err != nil {
		fmt.Println(err.Error())
	}

	json.Unmarshal(content, &data)
}

// the following are the actual CRUD endpoint functions
func GetQuote(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(data)
}

func GetQuoteById(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	// Get our params from the URL using Mux
	params := mux.Vars(r) 
	// using this atoi method to parses the string into an integer 
	requestId, _ := strconv.Atoi(params["id"])
	// Loop through collection of quotes and find one with the id from the params
	// the underscore is basically read as `for each item in the collection`
	for _, item := range data {
		if item.Id == requestId {
			json.NewEncoder(w).Encode(item)
			return
		}
	}
	json.NewEncoder(w).Encode(&Quote{})
}

func CreateQuote(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	quote := Quote{}
	_ = json.NewDecoder(r.Body).Decode(&quote)
	quote.Id = findHighestId() + 1

	data = append(data, quote)
	json.NewEncoder(w).Encode(quote)
}

func UpdateQuote(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	quote := Quote{}
	_ = json.NewDecoder(r.Body).Decode(&quote)

	// when you have the `index` defined, you have the actual index of the item from the splice
	for index, item := range data {
		if item.Id == quote.Id {
			// this is very similar to a splice in JavaScript (same idea)
			data = append(data[:index], data[index+1:]...)
			data = append(data, quote)
			json.NewEncoder(w).Encode(quote)
			return
		}
	}
}

func DeleteQuote(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	params := mux.Vars(r)
	requestId, _ := strconv.Atoi(params["id"])

	for index, item := range data {
		if item.Id == requestId {
			data = append(data[:index], data[index+1:]...)
			break
		}
	}
	json.NewEncoder(w).Encode(data)
}

The crud.go file is pretty straight forward. We’re going to have five functions for each of the CRUD functions that exist in the main.go file.

Code Explanations

Once again, from top to bottom, I’ll explain the code.

First, we import the JSON utilities from the Golang standard libraries so that we can work with our JSON data collection. Then, we open that JSON file, and we load it into a local variable called data. This will be our in-memory database for everything we’re doing later.

You will notice that we have created an item called a struct in Golang. This is used to demonstrate what a Quote will look like inside the application. We also have another function called findHighestId. This is a simple utility function used to find the highest id from the objects within the data collection. In Go, capitalization is very important. Because this function starts with a lowercase letter, the function is private to the package. A function that starts with an uppercase letter, on the other hand, is able to be accessed outside of the package. You will see this style used in the following crud functions.

See Also:  A Vue of Python

Our first crud function is for our get quote endpoint, and we perform a simple dump of all the data and send it back as JSON.

The next function is our get by id function. Here, we’re looking for an id to be passed in from the endpoint. We cast that variable into an integer, and then we compare it to all found ids within the data collection. If one happens to be found, we’re sending that one back to the endpoint as JSON. Note that we use mux here to access the parameters passed in on the URL.

Our third function is to create a new quote by utilizing the private function to find the highest ID of our quotes collection. Immediately after that, we use the returned max value function to increment that key one step up and assign that to the newly created quote. The newly-created quote passed in on the function is then appended to the data collection, and we return the data as JSON to the endpoint.

The fourth function is used for updating an item in the data collection. We create a For Loop here with two variables. The first variable is used for the indexer of the loop, and the second is the item in the loop. We use Go’s range function and turn our data into a collection which can be iterated upon.

Now we compare the id of each item in the collection to the quote’s id. If they match, we delete the existing data from the collection. Then, we use the quote variable and insert the updated item back into the data collection at the same point.

Our last function in this file is used to delete an item. The function is passing in an id, we’re going to loop over the data collection and check each item’s id against the passed-in one. If one matches, we use the append function on the collection and delete that item. Once that’s complete, we return the modified collection back to the endpoint as JSON.

Finally, we now have the actual JSON file.

api/data.json

   {
       "id": 5,
       "author": "Stephen King",
       "text": "Get busy living or get busy dying."
   },

This is just a small snippet, but all of the rows follow in this same fashion by having an ID, an AUTHOR, and a TEXT property.

Go to a Conclusion

And that’s all we need for a fully functional CRUD API built in Golang.

The goal of this project was to build a Go API with parity that matched with the original Quotes on Demand Node server. With just a little research on finding ways to use Mux for our routing and seeing how to manipulate collections, we were able to successfully complete our mission. Golang is a simple but powerful language and this project shows great potential for expansion.

For more information about the VueJS user interface used here, see Lean Mean Vue Machine found on the Keyhole Software blog. If you would like to read further about Go and it’s usage with a full scale application, or want to see how to connect it with a database, check out the Keyhole Software blog’s Go topic for more information.

Comments 1

  1. It would be easier to take this article seriously if you would realize that the name of the language is Go, not Golang.

What Do You Think?