spot_img
HomeArchitectureIt is time to "Go": our journey into grown-up code part 10

It is time to “Go”: our journey into grown-up code part 10

Welcome to the tenth article in our series on learning GO; All the previous articles in this series can be found on the links below:

  1. It is time to “Go” and learn how to code with Go
  2. It is Time to Go: our journey into grown-up code part 2
  3. It is Time to Go: our journey into grown-up code part 3
  4. It is Time to Go: our journey into grown-up code Part 4
  5. It is Time to Go: our journey into grown-up code part 5
  6. It is Time to Go: our journey into grown-up code Part 6
  7. It is Time to Go: our journey into grown-up code part 7
  8. It is Time to Go: our journey into grown-up code part 8
  9. It is Time to Go: our journey into grown-up code part 9

Our journey to date has covered a lot of ground, and we have slowly built up an understanding of several core features and concepts.  As a result, we are genuinely building firm foundations.

  • The core Variables types
  • Variadic variables
  • Redeclaration
  • Constants
  • inputs
  • Structs
  • Pointers
  • Interfaces
  • Function basics
  • Conditional statements, if statements and switch statements.
  • Loops, breaks, deferment, continue, goto, return and recover
  • Range
  • Importation of built-in functions (“fmt”, “sync”, “reflect”, “strings”, “strconv”, “math”, “net/html”, “html/template”)
  • Arrays and Slices

At the end of our last article, we extended our understanding of the “html” built in functions.  We effectively modified the basic website we created at the end of article 8 by adding a number of code generated pages and showed how we could create an input form for adding data to an environment, which was then displayed on another page in a table.

What are we looking at today?

Today is going to be CRUD,  no not that sort of crud,  hopefully it will not be “nonsense, that passes itself off asa smart twenty-something comedy” nor hopefully, will it be a substance that has accumulated several layers of dirt needing a good washing with soap.  We are talking about CRUD in a technical capacity; or to be precise CREATE, RETREIVE(READ), UPDATE and DESTORY.  These are the four basic operations for dealing or interacting with databases.  By the end of this and the next article I hope to have achieved something similar to the architecture shown below.  One of the first things we will need to do here is to deploy a MySQL database.  This is a simple enough task,  point your favourite browser at the following website https://dev.mysql.com/downloads/installer/ and download the version for your particular operating system.  You will be need to have an account with Oracle as the OpenSource Database has been a part of that companies stable of Databases for several years now, but this is a small price to pay for access to a powerful Database that is also free😊.  There are plenty of blog posts on how to install and configure MySQL so I will not be re-inventing the wheel in this article.  My working assumption is that you have installed MySQL and correctly configured it with a database to populate with data.

NOTE: we will not be using MYSQL in this article.

We now know enough to Go and be dangerous

As per our standard modus operandi, we will look at an architectural diagram of our application to gain a visual representation of what we are building.  We will have a

architecture
what are we building today?

Now before we start writing we are going do something new, so far we have been introduced to “go run” and “go build”.  Today we introduce “go get” this is used to download and install dependences to a package you are writing.  On our case “github.com/gorilla/mux.”  The mux” package implements a request router and dispatcher for matching incoming requests to their respective handler. The name mux” stands for “HTTP request multiplexer”.  Now if you have been reading ahead and looking at what functions the “net/http” supply in addition to the features we have already looked at you would have found that there is a http.ServeMux”,” mux.Router” in that package.  However, the gorilla package provides more flexibility in matching requests including support for variables in the URL path, regular expression route patterns and host-based routing.  For a deep dive on “Go Router decisions” read the excellent article be Alex Edwards, whose flow chart I have “borrowed”

MUX Chose flowchart
Nice flowchart on MUX chosing

To add this package into our go library you just enter the command “go get “github.com/gorilla/mux”” to add this to your local go pkg  library. As can be seen by the image below.

Folder proof
now we are installing extra packages from Github, how fancy are we?

We be now know were he start, yep with the “main.go” package, this is going to significantly longer than our previous ones, as we are actually starting to write useful things; rather than just being an example of how things work.  We have multiple functions and structs etc.

package main
import (
    "encoding/json"
    "fmt"
    "log"
    "math/rand"
    "net/http"
    "strconv"
    "github.com/gorilla/mux"
)

Our first code block is our importation section,  looks familiar,  but wait, why is “github.com/gorilla/mux” formatted like that rather than our usual format?  This is because in Go packages are imported using their full path. The full path of the “gorilla/mux” package is the physical location of the repository’s URL on. So, when “gorilla/mux” is imported into your Go code, you are actually importing it from the gorilla/mux repository on GitHub.

type book struct{
    ID string `json:"id"`
    Isbn string `json: "isbn"`
    Title string `json: "title"`
    author *author `json: "author"`
}
type author struct {
    firstname string `json: "firstname"`
    lastname string `json: "lastname"`
}
var books []book

Next, we create two structs one called “book” and a second called “author”,  the only thing of note here is that the author object in the “book” struct of a type called “*author”. This as we already know is a pointer, in this case it points to the author struct’s memory space and effectively adds the contents of the author struct to the book struct.  Finally we have created a variable called “books” that is a slice of “[]book”.

func main(){
    r := mux.NewRouter()
    books = append(books, book{ID: "1", Isbn:"123456", Title: "Worlds Best Striker", author: &author{firstname:"Erling", lastname: "Haaland"}})
    books = append(books, book{ID: "2", Isbn:"234567", Title: "Worlds Best Mid-Fielder", author: &author{firstname:"Kevin", lastname: "De Bruyne"}})
    r.HandleFunc("/books", getbooks).Methods("GET")
    r.HandleFunc("/books/{id}", getbook).Methods("GET")
    r.HandleFunc("/books", createbook).Methods("POST")
    r.HandleFunc("/books/(id)", updatebook).Methods("PUT")
    r.HandleFunc("/books/(id)", deletebook).Methods("DELETE")
    fmt.Printf("Starting server at port 8000\n")
    log.Fatal(http.ListenAndServe(":8000",r))
}

Our first function is the ubiquitous “main()” function,  we have created a variable “r” from then  “mux.NewRouter()” function name to shorten our need to rewrite “mux.NewRouter()” in front of every place that we have “r.” in the function.  next we append to entries to the “books” slice.   Next is the meat and two veg of the function, here we create several endpoints, each of which are associated with a specific function and HTTP method.  The last part of the function starts the server on port 8000 and tells us that it is being started.

func getbooks(w http.ResponseWriter, r *http.Request){
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(books)
}

This function is associated with the following endpoint in “func main()”,

r.HandleFunc("/books", getbooks).Methods("GET")

The function takes two arguments: http.ResponseWriter” and *http.Request” we have discussed these previously. the next line sets the content type of the response to JSON using the “w.Header().Set(“Content-Type”, “application/json”)” function. It then encodes the books” slice as JSON and writes it to the response using the json.NewEncoder(w).Encode(books)” function.

func getbook(w http.ResponseWriter, r *http.Request){
    w.Header().Set("Content-Type", "application/json")
    params := mux.Vars(r)
    for _, item := range books {
        if item.ID == params["id"]{
            json.NewEncoder(w).Encode(item)
            return
        }
    }
}

Our next function is called getbook().  Once again the function takes two arguments: “http.ResponseWriter” and “*http.Request”. Again we set the content type of the response to JSON using “w.Header().Set(“Content-Type”, “application/json”)” function. It then extracts the value of the “id” parameter from the request URL using the ”mux.Vars(r)” function. The function then iterates over the books slice and returns the book with the matching ID as JSON using the “json.NewEncoder(w).Encode(item)” function.

func createbook(w http.ResponseWriter, r *http.Request){
    w.Header().Set("Content-Type", "application/json")
    var book book
    _ = json.NewDecoder(r.Body).Decode(&book)
    book.ID = strconv.Itoa(rand.Intn(100000000))
    books = append(books, book)
    json.NewEncoder(w).Encode(book)
}

Our next function is called “createbook()”.  Once again the function takes two arguments: “http.ResponseWriter” and a “*http.Request”.  Our second line again sets the content type of the response to JSON using the w.Header().Set(“Content-Type”, “application/json”). We then decode the JSON payload from the request body and pass it into a new book struct using the json.NewDecoder(r.Body).Decode(&book) function. we then generate a random ID for the new book using the ”rand.Intn(100000000)” function and assigns the result to the ID field of the book struct. The new book is then appended to our “books” slice using the append(books, book) function. Finally, the new book object is encoded as JSON and written to the response using the “json.NewEncoder(w).Encode(book)” function.

func updatebook(w http.ResponseWriter, r *http.Request){
    w.Header().Set("Content-Type","application/json")
    params := mux.Vars(r)
    for index, item := range books{
        if item.ID == params["id"]{
            books = append(books[:index], books[index+1:]...)
            var book book
            _ = json.NewDecoder(r.Body).Decode(&book)
            book.ID = params["id"]
            books = append(books, book)
            json.NewEncoder(w).Encode(book)
            return
        }
    }
}

Our next function is called “updatebook()”. Again our function takes two arguments: “http.ResponseWriter” and “*http.Request” and sets the content type of the response to JSON using “w.Header().Set(“Content-Type”,”application/json”)”. Next, we extract the value of the “id” parameter from the request URL using “mux.Vars(r)”. The function then iterates over the current “books” slice and finds the book with the matching “ID”. Once our iteration has found the correct “id”, it removes the old book from the slice using “append(books[:index], books[index+1:]…)”. We then decode the JSON payload from the request body into a new “book” struct using “json.NewDecoder(r.Body).Decode(&book)”.  “book.ID = params[“id”]” sets the ID of the new “book” to the id of the old “book”. Finally, the new book is appended to the books slice using “append(books, book)” and once again the response is written as JSON using “json.NewEncoder(w).Encode(book)”.

func deletebook(w http.ResponseWriter, r *http.Request){
    w.Header().Set("Content-Type","application/json")
    params := mux.Vars(r)
    for index, item := range books{
        if item.ID == params["id"]{
            books = append(books[:index], books[index+1:]...)
            break
       }
    }
    json.NewEncoder(w).Encode(books)
}

Our final function is called “deletebook()”. This function once again takes two arguments: “http.ResponseWriter” and “*http.Request” and sets the content type of the response to JSON using “w.Header().Set(“Content-Type”,”application/json”)”. We then extract the value of the “id” parameter from the request URL using the “mux.Vars(r)”.  We then iterate over the ”books” slice and find the book with the matching ID. Once found, it removes the book from the slice using “append(books[:index], books[index+1:]…)” and finally, we write the updated list of books to the response as JSON using “json.NewEncoder(w).Encode(books)”.

Now as long as everything has been written correctly we should just be able to issue a “go run main.go” at the command line and receive the following response.

go run main.go
ready steady go – or more precisely go run main.go

If we now open a browser and point it to localhost:8000 we will receive a 404 error as shown below.

no website.
There is no website here. This is an API endpoint

This is because the is no HTML to display, this is expected as this is not an exercise in building a website, but an API.

So how do we confirm things are working as expected?

Let me introduce you to Postman.  Not the person that delivers your letters, but a rather useful tool to aid in the building and utilisation of APIs.  It simplifies each step of the API lifestyle.  We are not going to have a deep-dive into Postman in this article, if you need an overview on how to use the product, have a quick read of their site’s Overview in their Learning Center.  I am going to assume that you have some experience on using Postman.  If you remember we have created five functions that allow basic manipulation of our data, these are “getbooks”, “getbook”, “createbook”, “updatebook” and finally “deletebook”.  These functions relate to the following API calls “GET ALL, GET, CREATE, UPDATE and DELETE”.  We will be using Postman to verify that our code is working as expected.

The first API call we will be testing out is the GET ALL call.  To create this we simply point our command line at our URL “localhost:8000/books” and configure a simple “GET” and click “Send”.

Get-all in Postman
Get-all in Postman

If everything has worked correctly we should see in the response box our code created entries.

Get-all response
Our first API Call and it worked.

Next, we will test the simple “GET”, to return a single entry in our array.  To test this simply add a known “id” that is currently part of the array

get an individual record
Get an individual record

If this works correctly you should receive a response similar to below.

Get response
Proof we can cherry-pick

So we can return data that is already contained in the array, but what about expanding the array?  OK, let’s test the “CREATE” API call to add an entry to our dataset.  This is slightly different,  here we are using a different call, this time “POST”, and we also have to send some data to our application. Remove the ID from the URL, and change the “GET” to “POST”, select “RAW” to enable the data entry form and enter your data, finally change the “TEXT” dropdown to “JSON” and click “send”.

Create a new record
Creating a new record

Once again, if everything is working as expected you should receive a response similar to that shown below.

create response
Here is our response.

For belt and braces testing, let’s re-run our “GET ALL” API call again. If it has worked you should see the entry added to the array as shown below.  Here you can see that we now have a third entry with a randomly assigned “ID” number.

proof it is there
Here is the proof that we have added data.

Now let’s update our entry.  To do this we need to verify our “updatebooks” function and the “UPDATE” API call.  Here we are using “PUT” as we are modifying an already existing entry.  Remember to copy the “ID” of the necessary entry into URL.

updating a record
Here we are updating a record

If everything is successful, you will receive a response as below.

update response
oh Look, we have updated our Goalkeeper

I will leave the verification of the change in the array to you the reader. Our final test is to test “Deletebook”,  this will verify the “DELETE” API Call.  Configure Postman as shown below, substitute the “ID” and the Body data with your information.

delete a record
Deleting a record

After clicking send you should receive a response that shows that the entry has been removed.

Delete response
oh look it has gone

All successful.

Summary

This has been another long post and we have once again covered a lot of new ground and concepts.  In our next article, we will take what we have written today, and enhance it, place a Database behind it.  I can finally see the light at the end of this tunnel.  Personally, I am starting to regularly see patterns and am now actually thinking about how this can be used in other places.  I am finding it easier to understand the flow of Go code I am reading.  Perhaps I am finally starting to understand the language.

NEWSLETTER

Receive our top stories directly in your inbox!

Sign up for our Newsletters

spot_img
spot_img
spot_img
spot_img

LET'S CONNECT