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.
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:
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.
Explanation is very well done; keep sharing great post with us
ReplyDeleteBitcoin payment for e commerce development | Pay Bitcoin for Ecommerce Development | web design Hubli
Yes i am totally agreed with this article and i just want say that this article is very nice and very informative article.I will make sure to be reading your blog more. You made a good point but I can't help but wonder, what about the other side? !!!!!!Thanks latest web series
ReplyDeleteWhen you use a genuine service, you will be able to provide instructions, share materials and choose the formatting style. web hosting
ReplyDeleteIt is truly a well-researched content and excellent wording. I got so engaged in this material that I couldn’t wait reading. I am impressed with your work and skill. Thanks. Windows Hosting
ReplyDeleteJab Apki Gallery Ya Mobile Phone Se Photo Deleted Ho Jaati Hai Tab Aap Badi Asani Se Wapas Recovey Kar Shkte Hai Use Leaye Aap Ye Post Pad Shkte Hai Delete Photo Wapas Kaise Laye Saath Hi Ager Aap Jana Chate Hai My Name Ringtone Maker Ke Baare Mai Tho Aap Badi Asani Se Tho Ye Post Pad Shkte Hai Apne Naam Ki Ringtone Kaise Banaye
ReplyDeleteAger AAp Photo Ka Video Bana Chate Hai Mager Apko Video Editing Nhi Aate Tho Photo Se Video Banane Wala Apps Post Read Kar Shkte Saath Social Media Par Beutifull Photo Editing Kar Upload Kaise Kare Uske Leaye Photo Kaise Banaye Wale Post Read Kar Shkte Hai
ReplyDelete