Skip to main content
Engineering Notes · August 2014

A Markdown Static Site in Go (Martini)

Portrait of Komang Artha Yasa — technology leader, two decades building digital platforms across marketplaces, retail, logistics, fintech, and banking.

I had been playing with Go for a while. A bit of struggle in the beginning — the language design is genuinely different from anything I’d used before — but I love it, and I’ll definitely be using it for some upcoming projects.

It taste like a shiny Saturday morning at the beach with a cup of coffee and a cigarette.

The way I usually learn a new language is to port an existing simple piece of code I’ve written in another language. It tells me how the new language thinks. Here I ported a small Ruby/Sinatra app — a very simple static site engine driven by Markdown.

Project Structure

In your working dir at $GOPATH:

views/
├── master.html
└── homepage.md
main.go

File: main.go

package main

import (
    "fmt"
    "github.com/codegangsta/martini"
    "github.com/codegangsta/martini-contrib/render"
    "github.com/russross/blackfriday"
    "html/template"
)

func parseMarkdown(args ...interface{}) template.HTML {
    s := blackfriday.MarkdownCommon([]byte(fmt.Sprintf("%s", args...)))
    return template.HTML(s)
}

func main() {
    engine := martini.Classic()

    engine.Use(render.Renderer(render.Options{
        Directory:  "views",
        Layout:     "master",
        Funcs:      []template.FuncMap{{"pmd": parseMarkdown}},
        Extensions: []string{".md", ".html"},
        Charset:    "UTF-8",
    }))

    engine.Get("/", func(r render.Render) {
        r.HTML(200, "homepage", map[string]interface{}{"Title": "Golangers"})
    })

    engine.Run()
}

Three external packages are doing the heavy lifting: martini (a small Sinatra/Express-ish web framework for Go), martini-contrib/render (the template renderer), and blackfriday (the Markdown parser). fmt and html/template are standard library.

How the Renderer Is Wired

martini.Classic() gives us static file serving and routing out of the box. We then bolt the rendering middleware on with options:

  • Directory — where to look up template files.
  • Layout — the layout template.
  • Extensions — which file extensions count as templates.
  • Funcs — template functions; here we expose parseMarkdown under the alias pmd.

parseMarkdown itself is trivial — template.HTML over Blackfriday’s output, so we don’t double-escape the rendered Markdown in the layout.

Endpoint Routing

engine.Get("/", func(r render.Render) {
    r.HTML(200, "homepage", map[string]interface{}{"Title": "Golangers"})
})

Whenever a request hits /, the renderer looks for homepage in the views directory and returns HTTP 200 with HTML. The map of key/value pairs is the binding context for the template.

File: views/homepage.md

# Hello Gophers

We're pursuing the happy life of being programmers, may golang make it closer.

[@komang](http://twitter.com/komang)

A plain Markdown file. Nothing special.

File: views/master.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>{{ .Title }}</title>
</head>
<body>
  {{ yield|pmd }}
</body>
</html>

{{ .Title }} is replaced with the Title value passed from the route. yield is the rendered template body (homepage.md); piping it through pmd runs it through parseMarkdown so the Markdown becomes HTML.

Compile and Run

Resolve dependencies first:

go get .

For development I like gin for live reload:

go get github.com/codegangsta/gin
$GOPATH/bin/gin main.go

Then visit http://127.0.0.1:3000/.

Code Repository

Code lives on GitHub:

go get github.com/chazuka/go/markdowner
cd $GOPATH/src/github.com/chazuka/go/markdowner
$GOPATH/bin/gin main.go

Suksma.