Matias Pan

Matias Pan

Software Engineer

Contact Me

Building a basic blog with Golang

6 December 2016

Introduction

One of the best things Go has is the standard library and the tools that the Go team created. The package that I'm going to use today is called blog and it was made by Andrew Gerrand, one of the Go team members.

Although I will slightly modify the package to better suit my needs, the important stuff will all be the same.

Lets get started!

Initialization

Since this is a web app, I'll start by defining the webserver and registering a few basic URLs using the gorilla/mux router package, which is quite powerfull. Note: you can use the standard net/http package, but I like gorilla/mux better:

package main

import (
        "io/ioutil"
        "log"
        "net/http"

        "github.com/gorilla/mux"
)

func main() {
        r := mux.NewRouter()
        s := http.StripPrefix("/assets/", http.FileServer(http.Dir("./assets/")))
        r.PathPrefix("/assets/").Handler(s)
        r.HandleFunc("/", homepageHandler)
        log.Fatal(http.ListenAndServe(":8080", r))
}

func homepageHandler(w http.ResponseWriter, r *http.Request) {
        bts, err := ioutil.ReadFile("template/homepage.tmpl")
        if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
        }
        w.Header().Set("Content-Type", "text/html")
        w.Write(bts)
}

Let's go over this code. First of all we are creating a new gorilla/mux router with mux.NewRouter() so that we can register the URLs we want to that router.

Then we define a file server for the assets/ directory and all it's subdirectories, so we can serve urls like: /assets/css/styles.css or something like /assets/js/main.js.

Last but not least, we define a homepageHandler that renders the template template/homepage.tmpl whenever someone access the root url, and we serve the app on 8080.

The blog and present packages

The first thing you need to know about the blog package is that it uses the present format to render all the artciles that you write. The present package, built by the Go team as well, defines a specific format(similar to markdown but with a few more go-specific options) to write slides and articles. Have you've ever seen a Go talk from one of the Go team members? Well, the slides that they use were written using this format, and then rendered by the present command-line tool.

If you want to see what the format looks like, head over to the godoc page and see it for yourself. And to see a bit of these in action, here is the result of rendering this article. It looks nice right?

Using and modifying blog

The blog package renders all files that have the .article extension, to define where it can find this articles the package gives us a blog.Config. This structs has a few configuration fields, one of them is ContentPath, the path to the all .article files.

We are not going to use all the fields that these struct gives us. Instead, we'll use this:

const hostname = "localhost:8080/blog"
var config = Config{
        TemplatePath: "template/", // path to template files
        ContentPath:  "article/", // path to all articles
        Hostname:     hostname,
        BaseURL:      "//" + hostname,
        HomeArticles: 5, // the total articles to be shown on the homepage, we are not going to use it
        FeedTitle:    "Things floating in my mind", // title use to the RSS and Atom feed
}

Now that we have the appropiate configuration, we need to initialize a new blog server that handles all incoming requests related to the blog, like displaying the index of the blog and each of the articles that we add to the article/ directory.

The blog package defines a blog.Server struct that implements the http.Handler interface, meaning that it's capable to map incoming requests with go functions.

To create a server we use the blog.Config that we defined earlier:

server, err := blog.NewServer(config)
if err != nil {
        log.Println(err.Error())
}

Before we use the server to map urls, we need to modify the blog package.

By default, the package defines the following urls:

/ -> Shows a certain number of articles given by config.HomeArticles
/index -> Shows a list of all the articles
/{article} -> Shows the rendered article named {article}, like /go-maps-in-action

Since we are using the / url to show a homepage made by us, we are going to define the following urls:

/blog -> Shows a list of all the articles, like /index previously
/blog/{article} -> Shows the rendered article, like /{article} previously

Note: we don't have a "home" like the / url that the package has by default.

So, to map this urls with the blog server we define the following:

r.HandleFunc("/blog", func(w http.ResponseWriter, r *http.Request) {
    server.ServeHTTP(w, r)
})
r.PathPrefix("/blog/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    server.ServeHTTP(w, r)
})

Now we have to modify the blog package so that it uses only our urls. Let's go ahead and do that(you can find the blog package in $GOPATH/src/golang.org/x/tools/blog/ assuming you did a go get).

First we need to change the docPaths that the blog uses for each article, so in the line 212:

// This is what the package has:
s.docPaths[strings.TrimPrefix(d.Path, s.cfg.BasePath)] = d

// This is what we need:
s.docPaths[fmt.Sprintf("/blog%v", d.Path)] = d

Now we need to modify how the ServeHTTP method maps incoming requests.

This is what we have starting on line 381:

case "/":
        d.Data = s.docs
        if len(s.docs) > s.cfg.HomeArticles {
                d.Data = s.docs[:s.cfg.HomeArticles]
        }
        t = s.template.home
case "/index":
        d.Data = s.docs
        t = s.template.index

Now replace it with this:

case "/blog":
        d.Data = s.docs
        t = s.template.index
// Delete the case "/index"

With this modification we are deleting the "homepage" where it shows a certain number of articles, and we are specifying that the index is /blog.

Adding templates and articles

Ok, we are almost done. Now we need to add the .tmpl files that the blog package needs. These are:

root.tmpl -> root template
home.tmpl -> for showing the home blog(merged with root to use as index)
index.tmpl -> shows the list of articles
article.tmpl -> specifies a few things for rendering each article
doc.tmpl -> format used by present

Instead of creating our own, we are going to use the blog.golang.org templates to see the results faster. Go ahead and copy this files into your own template/ directory.

To use the default styles that they use, we have to import the static package that has all the styles and js files. So our final main.go file is:

package main

import (
        "io/ioutil"
        "log"
        "net/http"
        "strings"
        "time"

        "github.com/gorilla/mux"

        "golang.org/x/tools/blog"
        "golang.org/x/tools/godoc/static"
)

const hostname = "localhost:8080/blog"

var config = blog.Config{
        TemplatePath: "template/",
        ContentPath:  "article/",
        Hostname:     hostname,
        BaseURL:      "//" + hostname,
        BasePath:     "",
        HomeArticles: 5,
        PlayEnabled:  true,
        FeedTitle:    "Things floating in my mind",
}

func main() {
        server, err := blog.NewServer(config)
        if err != nil {
                log.Println(err.Error())
        }
        r := mux.NewRouter()
        s := http.StripPrefix("/assets/", http.FileServer(http.Dir("./assets/")))
        r.PathPrefix("/assets/").Handler(s)

        r.HandleFunc("/blog", func(w http.ResponseWriter, r *http.Request) {
                server.ServeHTTP(w, r)
        })
        r.PathPrefix("/blog/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                server.ServeHTTP(w, r)
        })
        r.HandleFunc("/", homepageHandler)
        r.Handle("/lib/godoc/", http.StripPrefix("/lib/godoc/", http.HandlerFunc(staticHandler)))
        log.Fatal(http.ListenAndServe(":8080", r))
}

func staticHandler(w http.ResponseWriter, r *http.Request) {
        name := r.URL.Path
        b, ok := static.Files[name]
        if !ok {
                http.NotFound(w, r)
                return
        }
        http.ServeContent(w, r, name, time.Time{}, strings.NewReader(b))
}

func homepageHandler(w http.ResponseWriter, r *http.Request) {
        bts, err := ioutil.ReadFile("template/myindex.tmpl")
        if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
        }
        w.Header().Set("Content-Type", "text/html")
        w.Write(bts)
}

Now go ahead and run go run main.go, head over to your localhost:8080/blog and you'll see a similar page to the index of the golang blog. If you want to see any articles, you can add .article files into the config.ContentPath and then access to them using the url formats that we specified before, remember?

Try it out!

Conclusion

You can see how easy it is to have a blog up and running with very little code. It may not be as fancy or configurasble as a blog built with something like Rails that has users with authentication and all that, but it's just enough to have your own personal blog, like this one!

I built this page with most of the code I showed you here, the only difference is that I used google app engine to deploy it, so it has a few differences, but the important part is all the same!

Hope you enjoyed it!