How I moved my website from Django (Python) to Go
This blog has run on many languages, starting by the one I know the most: PHP (with Symfony framework), trying to learn new things by migrating to Python (with Django framework) and is now running under Go.
The (very-micro-)framework I've wrote to learn
So this blog is now running under Gofast, a micro-framework I've wrote in order to learn the language. Its code is open-sourced on my Github account so you can take a look if you are interested in. This is really simple code that allows to run a simple web application (as this website). Golang comes with some really powerful packages that helps me a lot in this migration.
Here is a simple "Hello world" rendered using Gofast:
package main
import (
"github.com/eko/gofast"
)
func main() {
g := gofast.Bootstrap()
router := g.GetRouter()
router.Get("homepage", "/", func (c gofast.Context) {
response := c.GetResponse()
response.Write([]byte("Hello world"))
})
g.Handle()
}
You can see that here, we bootstrap the micro-framework and retrieve the context
structure.
From this structure, you can retrieve the router (as done in the example below), the request, the response and the templating.
Rendering templates with Pongo2 library
Gofast micro-framework templating is using Pongo2
library as it allows to use quite-same templates as Django ones.
Almost all functions are working (excepts date ones as it is really different in Go) so I do not have to rewrite all my templates for the migration.
Imagine you have a homepage.html
template, this library allows you to render a Jinja template (Django templating syntax) by simply using the following code:
var template = pongo2.Must(pongo2.FromFile("homepage.html"))
template.ExecuteWriter(pongo2.Context{
"request": context.GetRequest(),
"response": context.GetResponse(),
}, context.GetResponse())
Additionnally, you also pass your request, response and router objects to your view so you can retrieve a request parameter for instance by simply calling:
{{ request.GetParameter("name") }}
Map your database tables to a Golang struct using Goorp library
We now have to request our database (I use MySQL as it is an historic database) to obtain our articles, tags and other data we need to display on our website.
I choose to use Gorp library to map my database tables with a Go struct.
Start by creating your struct (here, it is my article table):
type Article struct {
Id int `db:"id"`
Title string `db:"title"`
Content string `db:"content"`
Url string `db:"url"`
Date string `db:"date"`
}
Then, you have to map your struct to a specific table. This can be done this way:
import (
"github.com/coopernurse/gorp"
_ "github.com/go-sql-driver/mysql"
"database/sql"
)
db, err := sql.Open("mysql", "user:password@tcp(host:port)/dbname")
if err != nil { log.Fatalln("Fail to connect to database", err) }
dbmap := &gorp.DbMap{Db: db, Dialect: gorp.MySQLDialect{"InnoDB", "UTF8"}}
dbmap.AddTableWithName(Article{}, "article").SetKeys(true, "id")
Now that our database is mapped to our struct, we can start querying on it and retrieve an array of Article struct:
var articles []Article
_, err := dbmap.Select(&articles, "SELECT * FROM article")
if err != nil { panic(err) }
Display my Github activity using Github JSON API
To do that, we also have to display some structs corresponding to the Github API structure and map the API results with our defined structs:
type GithubRepo struct {
Id int `json:"id"`
Name string `json:"name"`
Url string `json:"url"`
}
type GithubActor struct {
Id int `json:"id"`
Login string `json:"login"`
GravatarId string `json:"gravatar_id"`
Url string `json:"url"`
AvatarUrl string `json:"avatar_url"`
}
type GithubAuthor struct {
Email string `json:"email"`
Name string `json:"name"`
}
type GithubCommit struct {
Sha string `json:"sha"`
Author GithubAuthor `json:"author"`
Message string `json:"message"`
Distinct bool `json:"distinct"`
Url string `json:"url"`
}
type GithubIssue struct {
Number int `json:"number"`
}
type GithubPayload struct {
Ref string `json:"ref"`
Head string `json:"head"`
Commits []GithubCommit `json:"commits"`
Issue GithubIssue `json:"issue"`
}
type GithubEvent struct {
Id string `json:"id"`
Type string `json:"type"`
Actor GithubActor `json:"actor"`
Repo GithubRepo `json:"repo"`
Payload GithubPayload `json:"payload"`
}
Once the structs are ready, we can perform our API call and map the results to these structs:
import (
"net/http"
"encoding/json"
)
res, err := http.Get("https://api.github.com/users/eko/events")
if err != nil { panic(err) }
body, err := ioutil.ReadAll(res.Body)
events := make([]GithubEvent,0)
err = json.Unmarshal(body, &events)
if err != nil { panic(err) }
Quite simple, no? The json.Unmarshal()
method will map our results so easily.
And yes, it's another thing for my migration solved, easily, thanks to Golang native packages.
Generate blog posts RSS and Atom feeds using feeds library
Last thing I had to do to make my personal website run fully as it was before was to create a RSS and Atom feed for my blog posts. To do that, I've used the feeds library.
I simply have to retrieve my blog posts and form a feed item, like that:
import (
"github.com/gorilla/feeds"
)
// Retrieve the blog posts
var articles []Article
_, err := dbmap.Select(&articles, "select * from article order by id desc limit 10")
if err != nil { }
// Creates the feed
now := time.Now()
feed := &feeds.Feed{
Title: "Vincent Composieux - Blog",
Link: &feeds.Link{Href: "http://vincent.composieux.fr/blog"},
Description: "A web engineer blog posts",
Author: &feeds.Author{"Vincent Composieux", "vincent.composieux@gmail.com"},
Created: now,
}
var items = []*feeds.Item{}
for _, article := range articles {
datetime, _ := time.Parse("2006-01-02 15:04:05", article.Date)
items = append(items, &feeds.Item{
Title: article.Title,
Link: &feeds.Link{Href: fmt.Sprintf("http://vincent.composieux.fr/article/%s", article.Url)},
Description: article.Content,
Author: &feeds.Author{"Vincent Composieux", "vincent.composieux@gmail.com"},
Created: datetime
})
}
feed.Items = items
Now that we have our feed loaded with our articles feed items, we can simply ask it to generate a .ToRss()
or a .ToAtom()
XML string and write it to the net/http response:
rss, err := feed.ToRss()
if err != nil { panic(err) }
response := c.GetResponse()
response.Header().Set("Content-Type", "text/xml")
response.Write([]byte(rss))
Well, all of our frontend features are migrated into Go language, and moreover, it has allowed me to learn a lot about this language.
Some other Golang frameworks ressources you can try
Martini
, a little framework more complete than the mine: https://github.com/go-martini/martiniRevel
, a high productivity full-stack web framework: https://github.com/revel/revelBeego
, an open-source framework to build and develop your Go application: http://beego.me/
Hope it has be helpful for you. Do not hesitate to contact me if you have some additional questions or comment directly on this blog post.