Friday, November 14, 2014

Try web development in Go with Slothful Soda, a small web app in the GGAP Stack (Part 1)

Lately I've been learning web development in Google's fairly-new programming language, Go (also known as Golang), and overall I think it's definitely worth giving a try for web programming, so I recently made a small web app called Slothful Soda to show a sample of what full-stack web development in Go can look like. I call the stack I am using GGAP, which stands for Golang, Gorilla, Angular, Postgres.

Right now, while the Go user-base is very fast-growing, it isn't as popular as Node.js, and I don't get the vibe there is one hyperpopular web stack in the Go community like there is with the MEAN Stack (MongoDB, Express, Angular, Node) in the Node community, but there are several stacks to choose from. I picked the GGAP Stack because I think it's straightforward to pick up straight from learning Go itself.

This tutorial does assume you know the basics of Go and some Angular.js and have Postgres installed. If you are new to Go, you can learn the language quickly at tour.golang.org (I found it especially easy to learn from C++), if you are new to Angular, I recommend checking out the tutorials at scotch.io (also great if you are trying to figure out the MEAN Stack). As for Postgres, I really don't know that much about the database myself so you really just need it installed for this tutorial.

Also, since this tutorial teaches how to make a full web app from just knowing Go, JavaScript, and Angular, it'll throw a lot at you.  If you're looking for a more basic tutorial on working with Go web servers and Gorilla routing to get a better understanding of working with web servers in Go, I recommend checking out this tutorial on my blog.

Meet the GGAP Stack

Go: Go is a programming language made by Google that compiles to some very fast programs and uses a lot of great features used in other languages, like structs, anonymous functions, and type inference. It's especially notable for having really intuitive support for concurrent programming, but we won't be talking about that in this tutorial.

Gorilla: Gorilla is a series of Go packages that that provide a lot of functionality for your web apps. Some of these features include the mux package for providing routing for your web app that feels similar to Express.js's routing in Node, the websocket package, which provides an interface for WebSocket communication (stuff like chat rooms and real-time online games), and the session package for giving your web apps support for sessions. Today we will be focusing on the mux package.

Angular.js: Angular is a very popular front-end web framework developed by Google, and in Angular instead of the style of using jQuery for DOM manipulation, you can put the presentation logic straight into your HTML while using JavaScript only for stuff that's behind the scenes like AJAX requests. Angular.js has a pretty big ecosystem to learn, but its popularity means you can find tons of resources for learning how to use it.

PostgreSQL/Postgres: Last but not least is our database for this web app, PostgreSQL. I'm actually new to Postgres myself, so I can't say much about why it's awesome but what I do know is it's a relational SQL database and I've heard good stuff about its speed. I wanted to use a relational database for this instead of a NoSQL database like MongoDB because I felt that the row/table structure of a relational database pairs well with Go's structs. However, there is a MongoDB driver for Go called mgo, so you can definitely do MongoDB in Go too.

The story of Slothful Soda

This web app is about a hibiscus-flavored soda called Slothful Soda, which a group of sloths living by the Cambridge Fresh Pond invented because of how popular playing Ultimate was in the parks around Boston. They knew these athletes were thirsty from their games, so they created their brand of hibiscus-flavored soda to deliver to parks in the Boston area by flying to those parks on quadcopters (DISCLAIMER: This is a fictional soda brand and there are no sloths living at the Cambridge Fresh Pond and definitely no sloths who know how to operate a quadcopter. Don't tell your friends who are likely to believe stories from The Onion about this blog post because if sloths figure out how to use quadcopters it means the Apocalypse is here and we really don't need any more bogus Apocalypse theories).

The places they want to deliver to are:

-The Residential Quad at Tufts University, at 42.408565 degrees latitude, -71.121765 degrees longitude
-Hodgkins Park in Somerville, at 42.399566 degrees latitude, -71.124595 degrees longitude
-Danehy Park in Cambridge, at 42.388306 degrees latitude, -71.137507 degrees longitude
-and Lederman Park in Boston, at 42.363649 degrees latitude, -71.071774 degrees longitude

But the sloths need to get the word out where they deliver their soda, so they needed a website. They got their claws on Go and Postgres, and after installing them, they made a web app to tell people how many of their locations are within some number of miles of the Cambridge Fresh Pond.

To see the app in action, you can swing by andyhaskell.github.io/Slothful-Soda, and to see all my source code for this web app, go to github.com/AndyHaskell/Slothful-Soda.

Getting started with a Hello world HTTP server

Let's start with a hello world HTTP server. Go to the src subdirectory your GOPATH and make a directory called slothful-soda. Go into that directory and write a server.go file that contains the following code:
package main

import (
    "fmt"
    "net/http"                                                        //1
)

func main(){
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){//2, 3
        fmt.Fprintf(w, "Hello world!") 
    })

    fmt.Println("Starting server")
    http.ListenAndServe(":1123", nil)                                 //4
}
                                                                                
Here's what it does:

1. For making our HTTP server, Go has a built in net/http package.

2. net/http handles HTTP requests to specific routes with http.HandleFunc, which takes in the route we want to handle requests to and a function for handling the request. We are handling requests to the “/” route, which in Go's http package handles all requests.

3. The function takes in an HTTP request and an I/O structure called a ResponseWriter that makes an HTTP response, which in this function is the message “Hello world!”

4. Once we have our server defined, we listen for HTTP requests on a port using http.ListenAndServe. For our web app, we will be listening on Port 1123.


Run go install and then run the slothful-soda command and in your browser go to localhost:1123 and you should get:


This is a diagram of the server:


Awesome! Now we have a Go web server. Next, let's add an images directory.

Serving images on our server

In your slothful-soda directory, make a directory called public and then in that directory, add directories called images, partials, scripts, and styles. This public directory is where we will be serving files used on the front-end, like images, partials for Angular.js rendering, client-side JavaScript, and CSS stylesheets.

Download these pictures and put them in the images directory as sloth.jpg and hibiscus.png.



Then in server.go in the slothful-soda directory, add this line just after the HandleFunc rule we made earlier.

http.Handle("/images/", http.StripPrefix("/images/",
                          http.FileServer(http.Dir("public/images"))))
                                                                                

This code uses http.Handle, which takes in a route and a Handler to process the requests. The route is “/images/”, which means we want the Handler to process any requests that start with “/images/”, such as localhost:1123/images/sloth.jpg.

http.FileServer creates a Handler that serves files from a specified directory, which in our case is public/images. So when the we get a request to a URL that starts with “/images/” like “localhost:1123/images/sloth.jpg”, we look in the public/images directory to find the image and if the image exists, we serve the image.

http.StripPrefix gets rid of the “/images/” part of the request so when we are looking in the images directory the FileServer handler only uses the parts of the request URL after “/images/”. Without it, if we requested localhost:1123/images/sloth.jpg, the FileServer would look for a file “public/images/images/sloth.jpg”, but with StripPrefix, the FileServer works correctly, looking for sloth.jpg in “public/images/sloth.jpg”.

Taken together, this call to http.Handle makes it so requests that start with “/images/” are handled by serving an image in the public/images directory.

This is a diagram of what that looks like:


So if we request localhost:1123/images/sloth.jpg, we get:


And if we change the “Hello world!” message to

"<div><img src=\"images/sloth.jpg\" width=\"300px\" height=\"400px\"/>Hello world!</div>"

we get this when we request localhost:1123:


Making our server serve Angular.js partials, JavaScript, and CSS works the same way, so we are going to bundle all of them into one function called addStaticRoutes:

func addStaticRoutes(){
    http.Handle("/partials/", http.StripPrefix("/partials/",
                http.FileServer(http.Dir("public/partials"))))
    http.Handle("/scripts/", http.StripPrefix("/scripts/",
                http.FileServer(http.Dir("public/scripts"))))
    http.Handle("/styles/", http.StripPrefix("/styles/",
                http.FileServer(http.Dir("public/styles"))))
    http.Handle("/images/", http.StripPrefix("/images/",
                http.FileServer(http.Dir("public/images"))))
}
                                                                                

And in the main function, we can now replace the images handler we had before with a call to addStaticRoutes().

Creating a Location struct

For our next step we are going to get cracking on giving this web app some data, so we are going to need a data structure for the locations where the sloths will be delivering their sodas, so let's make a Location struct. In the slothful-soda directory, make a new file called Location.go and in it add this code:
package main

type Location struct{
    Id int
    Name string
    Lat float64
    Lng float64
}

func initLocation(name string, lat, lng float64) *Location{
    return &Location{Name: name, Lat: lat, Lng: lng}
}
                                                                                
For this code, we are just defining a Location struct that will to represent the locations in the database. We also created an initLocation function that gives us a pointer to a new location structure (with the ID number uninitialized since the Postgres database will handle that for us).

Creating our database table and using GORP

Now we are going to need to go get a couple packages for working with our Postgres database.

First, run go get github.com/lib/pq to get a Postgres database driver for Go to work with.

Then, run go get github.com/coopernurse/gorp to get GORP.

What is GORP, you ask? GORP stands for Go Relational Persistence and it is a Go package used for smoothly marshaling your data between Go structs and database rows. It wraps a lot of database functionality, so you don't have to write as much SQL (in this web app we only use one line of SQL).

I think it is kind of like Mongoose is for Node/MongoDB, where it provides a convenient interface for a lot of database functionality. As for the pq module, we won't use it directly, but rather GORP will use pq to communicate with the database.


To get started with our database, make a file in the slothful-soda directory and make a file called initDB.go. In the file add the code:
package main

import ( 
    "database/sql"                                                     //1
    "log"

    "github.com/coopernurse/gorp"                                      //2
    _ "github.com/lib/pq"

)

func initDB() *gorp.DbMap{                                             //3 
    db, err := sql.Open("postgres", "postgres://yourPostgresLoginInfo")//4
    if err != nil {
        log.Fatal(err) 
    }
    
    err = db.Ping()                                                    //5 
    if err != nil {
        log.Fatal(err) 
    }

    dbMap := &gorp.DbMap{Db: db, Dialect: gorp.PostgresDialect{}}      //6

    return dbMap
}
                                                                                
and at the start of the main function in server.go, add this code at the beginning to make sure initDB ran properly:

db := initDB()
db = db
                                                                                

If you gave the right login information for your Postgres database, your server should start, indicating you had no problem connecting. Otherwise you should get an error saying what went wrong.


Here's what's going on:

1. For working with SQL databases like Postgres, Go gives you the standard database/sql library

2. We are also importing pq and GORP. For pq, we are importing it as _ because we aren't using pq directly; it is just there so when we are opening the database Go can use the Postgres driver.

3. initDB returns a pointer to one of GORP's structures called a DbMap, which is a structure in GORP that maps between the database and your Go structs and will be used for communicating with our database.

4. We connect to our Postgres database with sql.Open, giving it the string “postgres” to tell sql.Open we are opening a Postgres database and then giving a string for connecting to your Postgres database.  To connect properly, you will need to replace "postgres://yourPostgresLoginInfo" with a login string with your actual Postgres login information.

5. After we open the database, we ping the database to catch any errors from during the login, like getting the password wrong.

6. Finally, we make a DbMap for the database we opened. The dialect we are giving the DbMap is GORP's “PostgresDialect”, which basically is telling GORP we are using a Postgres database.

Making the database table

All right, so we have a DbMap, so now we are going to make a database table of Locations. First, we need to create a TableMap in GORP, which is a data type in GORP for representing database tables.


So first, we will make the TableMap by adding this code in initDB just after where we initialize our DbMap:

locationsTable := 
  dbMap.AddTableWithName(Location{}, "locations").SetKeys(true, "Id") 
                                                                                

AddTableWithName is used to create a TableMap for a certain data type in your Go code. For this struct we specified that we wanted our TableMap to work with the Location data type and we named the database table “locations”.

After AddTableWithName, we have the function call .setKeys(true, “Id”), which will make it so the Id field of our locations database table will automatically increment with each location added to the table.


Then we will want to make sure we aren't getting null values for the name and latitude and longitude coordinates of our locations, and we want to make sure there are no duplicate names for different locations in the database, so we will add this code after AddTableWithName:
//               1,               2,             3
locationsTable.ColMap("Name").SetNotNull(true).SetUnique(true)
locationsTable.ColMap("Lat").SetNotNull(true)
locationsTable.ColMap("Lng").SetNotNull(true)
                                                                                
1. TableMap.ColMap() gives us a ColumnMap, which is a GORP structure that corresponds to a column of the database table.

2. ColumnMap.SetNotNull() is used to specify that for the column in the database table we don't want that column to accept null values.

3. Finally, ColumnMap.SetUnique() is used to specify that for the column in the database table we only accept unique values.


Note that all of these things are things we could be doing in SQL, but GORP is making it so we can keep our work on building the locations database table all in Go (NOTE: If you already have a database table named locations for a different project, you might want to rename or back up that table for now since this next section has code that will delete the locations table in your database).

Now, for the last part of creating the database table, add this code after the calls to SetNotNull and SetUnique:
err = dbMap.DropTablesIfExists()     //1 
if err != nil {
    log.Fatal(err)
}

err = dbMap.CreateTablesIfNotExists()//2
if err != nil { 
    log.Fatal(err)
}
                                                                                
1. First, we get rid of the locations table if one already exists (such as from previously running the server) using dbMap.DropTablesIfExists, which does exactly what it says on the tin.
2. Another self-explanatory function, dbMap.CreateTablesIfNotExists creates our database table.


Run go install and then run slothful-soda and if you look at your database you should have a database table without having written any SQL!


Here's a diagram of what happens when the database table is being created:


Now to add some data to the table!

All right, so we have an empty database table, so now let's get this table populated. First, let's make a populateLocations function in initDB.go that takes in our DbMap and inserts some Location structs into the database.
func populateLocations(locationsMap *gorp.DbMap) {                       //1
    tuftsResQuad := initLocation("Tufts Res Quad", 42.408565, -71.121765)//2
    hodgkinsPark := initLocation("Hodgkins Park", 42.399566,-71.124595)
    danehyPark   := initLocation("Danehy Park", 42.388306,-71.137507)
    ledermanPark := initLocation("Lederman Park", 42.363649,-71.071774)

    err := locationsMap.Insert(tuftsResQuad, hodgkinsPark,
                               danehyPark, ledermanPark)                 //3

    if err != nil {
        log.Fatal(err)
    }
}
                                                                                
Here's what's going on:

1. Our populateLocations function takes in a DbMap for adding our Locations into the database.

2. We create one Location struct for each location we want in the database.

3. We add the Locations into the locations table of our database using the DbMap.Insert function.

Note that the DbMap knows to add the Locations to the locations table because our call to dbMap.AddTableWithName earlier associated the Location struct with the locations table.


Now, at the end of initDB, right before return dbMap, add the line:

populateLocations(dbMap)


And then run go install and slothful-soda and you should see that your locations table has been populated.


 
Creating our Gorilla router

Now that we have our database table, we're going to want a /locations route, which our Angular app will use for getting the data from the table, and a catch-all / route to serve our Angular app, which will have everything users can see.

Since we are making these routes, now is a good time to move our routing logic for the /locations and catch-all / routes from the basic Go http routing to a Gorilla router.


First, we will need to go get github.com/gorilla/mux to get Gorilla's mux package.

Then in the slothful-soda directory, create a file called router.go and in it add in this code:
package main

import ( 
    "fmt"
    "net/http"

    "github.com/gorilla/mux"
)

func indexRoute(w http.ResponseWriter, r *http.Request){    //1 
    fmt.Fprintf(w, "<div><img src=\"images/sloth.jpg\" width=\"300px\" height=\"400px\"/>Hello world (now routed with Gorilla)!</div>")
}

func locationsRoute(w http.ResponseWriter, r *http.Request) {//2 
    fmt.Fprintf(w, "Locations will be displayed here")
}

func initRouter() *mux.Router{ 
    r := mux.NewRouter()                                     //3
    r.HandleFunc("/locations", locationsRoute)               //4 
    r.HandleFunc("/{url:.*}", indexRoute)                    //5

    return r
}
                                                                                
The initRouter function creates our Gorilla router. Here's how it works:

1. indexRoute is a handler function for serving our index page, which for now has the picture of the sloth as a placeholder.

2. locationsRoute is a handler function for serving our locations data, which for now has “Locations will be displayed here” as a placeholder.

3. We initialize a Gorilla mux router with mux.NewRouter.

4. Much like HandleFunc in Go's standard http package, HandleFunc in gorilla/mux takes in a route and a handler function. In this line of code, we are telling the router to handle requests to “/locations” with the locationsRoute function.

5. We are creating a catch-all route in the Gorilla router to handle all requests in the router that are to a URL other than “/locations”. The route string we are using is “/{url:.*}”, which demonstrates how routes in Gorilla, much like routes in Express, are able to have variables in the path that can be matched with regular expressions. “/{url:.*}” means “match any path that is a / followed by anything that matches the regular expression .* (which means 0 or more characters besides newline)”.

So localhost:1123 matches that because the path given would be “/”, which is / followed by 0 characters. localhost:1123/some-other-path would match because the path “/some-other-path” would be / followed by several characters. But localhost:1123/locations would just be matched to the “/locations” route, not the catch-all route.


Now to add the router in, go to server.go and after db = db add the line:

router := initRouter()


Then replace

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){ 
    fmt.Fprintf(w, "Hello world!")
})
                                                                                
with the line:

http.Handle("/", router)


Between http.Handle("/", router) and addStaticRoutes(), our routing logic can now be summed up as:

If a request is to /images/, /partials/, /scripts/, or /styles/, serve the requested file statically from public. If the request is to anything else (remember in Go's standard http package / is the catch-all route), have our Gorilla router handle the request.”

Now run go install and slothful-soda and if you request localhost:1123 you should get:


And if you request localhost:1123/locations you should get:


And now our routing logic is:


Getting our data

Now let's make it so the /locations route fetches our locations data. For the locationsRoute function, we are going to need the DbMap that we generated in initDB to access the database, so to do that, we are going to pass the DbMap into initRouter.

Then, we will pass the DbMap into a function that creates a locationsRoute function that will use the DbMap.

So here is what the code will look like in router.go:
package main

import (
    "encoding/json"                                                    //1
    "fmt"
    "log"
    "net/http"

    "github.com/coopernurse/gorp"                                      //2
    "github.com/gorilla/mux"
)

func indexRoute(w http.ResponseWriter, r *http.Request){
    fmt.Fprintf(w, "<div><img src=\"images/sloth.jpg\" width=\"300px\" height=\"400px\"/>Hello world (now routed with Gorilla)!</div>")
}

func makeLocationsRoute(dbMap *gorp.DbMap) func(http.ResponseWriter, *http.Request){
    return func(w http.ResponseWriter, r *http.Request) {              //3
        var locations []Location
        var locationsJSON []byte

        _, err := dbMap.Select(&locations, "SELECT * FROM locations")  //4

        if err != nil {
            log.Fatal(err)
        }

        locationsJSON, err = json.Marshal(locations)                   //5

        if err != nil {
            log.Fatal(err)
        }

        fmt.Fprintf(w, "%s", locationsJSON)                            //6
    }
}

func initRouter(dbMap *gorp.DbMap) *mux.Router{
    locationsRoute := makeLocationsRoute(dbMap)

    r := mux.NewRouter()
    r.HandleFunc("/locations", locationsRoute)                         //7
    r.HandleFunc("/{url:.*}", indexRoute)

    return r
}
                                                                                
Here's what's going on:

1. For displaying the data we are getting from our database, we will be using Go's standard encoding/json package.

2. Since we need to use the DbMap, we are importing GORP in router.go.

3. makeLocationsRoute takes in a DbMap and puts that makes a handler function using that DbMap. This handler function will fetch our locations from the database and then output the locations as JSON.

4. We use DbMap.Select to run the query “SELECT * FROM locations” to get all of our locations. These locations are converted to Location structs.

5. json.Marshal converts our Location slice to a JSON encoding of the data.

6. Finally the handler function outputs the JSON encoding of the data.

7. We have our Gorilla router handle requests to “/locations” with the handler function generated in makeLocationsRoute.


After putting that code into router.go, in server.go, get rid of the line db = db and replace

router := initRouter()

with

router := initRouter(db)


Run go install and slothful-soda and then go to localhost:1123/locations and you should get:

  
Here is what the routing looks like for getting the locations:


 
Serving our main page

Now the only change left is to replace the placeholder HTML we serve in the Gorilla router's catch-all route with the HTML for our Angular.js app. We will actually make the Angular.js app in Part 2 of this tutorial, so for now all we need is a placeholder HTML file that will be served by the catch-all route's handler.

In the slothful-soda directory, make a directory called views and in it add an index.html file with this HTML:
<!DOCTYPE html>
<html> 
  <head>
    <title>Slothful Soda coming soon!</title> 
  </head>
  <body> 
    <div>
      <img src="/images/hibiscus.png" /><h1>Slothful Soda Coming Soon!</h1> 
    </div>
  </body>
</html>
                                                                                
And in router.go, replace the indexRoute function's code with:
func indexRoute(w http.ResponseWriter, r *http.Request){
    http.ServeFile(w, r, "views/index.html")
}
                                                                                
http.ServeFile(w, r, "views/index.html") has the file passed into the function be served as the HTTP response, so that function will serve views/index.html as the response for requests to our catch-all route.


Now if we run go install and slothful-soda and go to localhost:1123, we should get:


Now we have written every part of the web app that uses Go, so I will be stopping Part 1 of the tutorial here. In Part 2, we will be writing the Angular.js front end, which will communicate with the Go back end we wrote in this tutorial.

*The mascot at the top of the page is the Go Gopher, which was drawn by Renee French.

1 comment :