gobuffalo

Building a web page from scratch can feel like something complicated and time-consuming. It sure can, unless we are able to split our work into multiple smaller steps by extending the functionality with every iteration. Let's take a trip with Go Buffalo framework and build a business-card website with baby steps.

Our first goal would be to create a minimalistic website with three pages: welcome, resume, and contact. At first, we will be cool with all the content being static, hard-coded texts - we need to understand how Go Buffalo renders its templates, how can the data be passed to them and how can we create a proper navigation.

Start working with Buffalo

Note Since we want to explore the project that is rapidly evolving, I recommend installing development branch instead of master, just to see all the latest changes, features and bug-fixes.

It all starts with an empty project, but with buffalo command (available once you go get -u github.com/gobuffalo/buffalo/buffalo) it's not as empty as you think:

$ buffalo new business-card

Once you run that command, a basic website is being generated (alongside with some front-end part), so you could actually spin it up and see something working already. Before doing that, however, we need to setup a database as well. In the first step of development, we are not going to make any DB connections, but it will come in handy soon (and by default the application will not work, since it tries to establish a connection when handling an HTTP request). The fastest way to do this is run a Docker container using postgres image:

$ docker run --name some-postgres -e POSTGRES_PASSWORD=mysecretpassword -p 15432:5432 -d postgres

Database connection details (port and user password in this case) must be updated in database.yml file, since it is used to establish the connection:

# database.yml
development:
    dialect: postgres
    database: business-card_development
    user: postgres
    password: mysecretpassword
    host: 127.0.0.1
    port: 15432
    pool: 5

test:
    url: {{envOr "TEST_DATABASE_URL" "postgres://postgres:mysecretpassword@127.0.0.1:15432/business-card_test?sslmode=disable"}}

production:
    url: {{envOr "DATABASE_URL" "postgres://postgres:mysecretpassword@127.0.0.1:15432/business-card_production?sslmode=disable"}}

Once we get the database image to run, we need to create a database called business-card_development (as stated in the config above), using a command:

$ createdb -U postgres -h 127.0.0.1 -p 15432 business-card_development

At this point we should be ready to go and start Buffalo server in development mode (run it from business-card directory):

$ buffalo dev
...
Starting application at :3000
...

Basic Buffalo structure

Starting from scratch is hard, but starting from pre-generated project structure might be even harder, as you need to understand what goes where and how does it work. At this point we will be interested in three directories: actions that contain handlers for HTTP requests and definition of routes, assets to change CSS styles and templates for HTML structure of the pages.

First, we take a look on the only HTTP handler that is created for us, called HomeHandler that returns the welcome page and lists all available routes:

package actions

import "github.com/gobuffalo/buffalo"

// HomeHandler is a default handler to serve up
// a home page.
func HomeHandler(c buffalo.Context) error {
    return c.Render(200, r.HTML("index.html"))
}

Not too complicated, right? Actually, this is as simple handler as you can get. Spoiler alert, to pass data into a template you need to Set(..) appropriate values in buffalo.Context, which means you can add them in middlewares keeping your final handler (with some business logic) neat and clean. Adding next handlers should be pretty easy as well, so let's do that straight away, as at this point they will differ only by their names and template names (that .html file name). As I'm not a fan of permanently deleting stuff and like to keep a reference to something that works, I prefer to rename the HomeHandler to something like RoutesHandler, just in case. Next, we add HomeHandler (our own) ResumeHandler and ContactHandler:

// actions/home.go
...
func HomeHandler(c buffalo.Context) error {
    return c.Render(200, r.HTML("home.html"))
}

// actions/resume.go
...
func ResumeHandler(c buffalo.Context) error {
    return c.Render(200, r.HTML("resume.html"))
}

// actions/contact.go
...
func ContactHandler(c buffalo.Context) error {
    return c.Render(200, r.HTML("contact.html"))
}

This was fun, but we haven't bound that handlers to any specific paths in our application. We do that in actions/app.go file, which acts as an entry point and lists all the routes in the same place:

// actions.app.go
...
func App() *buffalo.App {
    if app == nil {
        app = buffalo.Automatic(buffalo.Options{
            Env:         ENV,
            SessionName: "_business-card_session",
        })
        ...

        app.GET("/", HomeHandler)
        app.GET("/resume", ResumeHandler)
        app.GET("/contact", ContactHandler)

        app.GET("/routes", RoutesHandler)

        app.ServeFiles("/assets", packr.NewBox("../public/assets"))
    }

    return app
}

Again, everything that you see in App() function is self-explanatory and you don't have to read the docs to understand it. Just add GET path with a handler and you are good to go. What that function does, it also helps a lot with testing your handlers. As we renamed home.go to routes.go, we did the same with home_test.go (-> routes_test.go) we still can see how a test for the handler should look like:

// actions/routes_test.go
package actions_test

func (as *ActionSuite) Test_RoutesHandler() {
    res := as.HTML("/routes").Get()
    as.Equal(200, res.Code)
    as.Contains(res.Body.String(), "Welcome to Buffalo")
}

You can see that we try to access our website on /routes path and check if its body contains some expected text. It's important to note that the test is actually a function on some ActionSuite struct, and what happens there is the reason why aforementioned App() is so good for you:

// actions/actions_test.go
package actions_test
...
type ActionSuite struct {
    *suite.Action
}

func Test_ActionSuite(t *testing.T) {
    as := &ActionSuite{suite.NewAction(actions.App())}
    suite.Run(t, as)
}

When ActionSuite is initialized, it creates a temporary version of our application (using suite library, made specifically for Buffalo) and binds all the routes to their paths (as defined in App()). As we don't have anything in our templates (we don't even have them at this point) yet, let's pretend that testing is not that important at the moment.

Templates

You may not believe that, but assuming that you know how to write a simple HTML, that was the hard part! Now we'll create four HTML files: one common base and one for each page in our application. Before that, we'll see how the templates work for routes.html (again, renamed from index.html for consistency). If you take a deeper look at what happens in the only line of RoutesHandler, you see r.HTML(..) function. In the same directory, there is a file called render.go which defines the mysterious r:

// actions/render.go
var r *render.Engine

func init() {
    r = render.New(render.Options{
        // HTML layout to be used for all HTML requests:
        HTMLLayout: "application.html",

        // Box containing all of the templates:
        TemplatesBox: packr.NewBox("../templates"),

        // Add template helpers here:
        Helpers: render.Helpers{},
    })
}

The render.Engine contains a configuration for how the templates will be rendered. HTMLLayout is a root template (which we'll use to create a common base), TemplatexBox points to a templates directory (where all HTML is kept) and Helpers allows you to create your own template helper functions (not necessary at this point). You can also overwrite the default parent template by passing multiple file names to the r.HTML(..) and we might want to do that since we like how /routes look, but might not be in the same style we want our /resume to be. After renaming application.html (which is a current base) to old_application.html, our RoutesHandler looks like this:

// actions/routes.go
func RoutesHandler(c buffalo.Context) error {
    return c.Render(200, r.HTML("routes.html", "old_application.html"))
}

Since we are using a custom base, we add it right after child template. Now we can play around with application.html and create HTML templates for the rest of the site:

// templates/application.html
<html>
    <head>
        <meta charset="utf-8">
        <title>Paweł Słomka</title>
        <link rel="stylesheet" href="/assets/application.css" type="text/css" media="all" />
        ...
    </head>
    <body>
        <div>
            <header>
                <div>
                    <h1 class="title">Paweł Słomka</h1>
                    <h2 class="subtitle">
                    My very personal business card.
                    </h2>
                </div>
            </header>

            <%= partial("flash.html") %>
            <%= yield %>
        </div>

        <script src="/assets/application.js" type="text/javascript" charset="utf-8"></script>
    </body>
</html>

There are a few important things to understand here. First of all, we see that /assets/css/application.scss (rendered to /assets/application.css) and /assets/js/application.js (compiled to /assets/application.js) are available in the template. Cool! Also, we can add other templates (eg. some commonly used elements) using <%= partial(TEMPLATE-PATH) %>. Finally, placing the contents of child template happens using <%= yield %> directive. Everything defined in home.html goes right here. At this point, let's keep our pages small not do get distracted from what we want to accomplish:

// templates/home.html
This is home page!

// templates/resume.html
This is resume page!

// templates/contact.html
This is contact page!

Now let's take this baby for a spin (make sure you have buffalo dev running in one of the terminals):

$ curl localhost:3000
<html>
    <head>
        ...
        <title>Paweł Słomka</title>
    ...
    </head>
    <body>
        ...
        This is home page!
        ...
    </body>
</html>

(business-card) $ curl localhost:3000/resume
<html>
    <head>
        ...
        <title>Paweł Słomka</title>
    ...
    </head>
    <body>
        ...
        This is resume page!
        ...
    </body>
</html>

(business-card) $ curl localhost:3000/contact
<html>
    <head>
        ...
        <title>Paweł Słomka</title>
    ...
    </head>
    <body>
        ...
        This is contact page!
        ...
    </body>
</html>

OK, all the pages are there, let's go for some navigation menu to make them useful.

Semi-smart navigation

Creating a simple navigation is easy, all you need to do is add a bunch of links and show them on the page:

// templates/_menu.html
<nav class="menu">
    <a href="/"><div class="menu__item">Home</div></a>
    <a href="/resume"><div class="menu__item">Resume</div></a>
    <a href="/contact"><div class="menu__item">Contact</div></a>
</nav>

The problem is, that you might want to mark a currently selected option so that the users know where they are at all times. Now it's the time where not-deleting routes-related files finally pay off - that view lists all paths and shows their names. That means, that at all times we can recognize what handler is being used based on a current path name. In Buffalo, all routes have RouteInfo that is their definition, and one of the fields available there is a string PathName. On the other hand, all handlers have current_route in their context, which contains that information as well. All we need to do is make those things meet and add some CSS class if the current route matches a menu item path:

// templates/_menu.html
<nav class="menu">
    <a href="/"><div class="menu__item<%= if (current_route.PathName == "rootPath") { %> menu__item--active<% } %>">Home</div></a>
    <a href="/resume"><div class="menu__item<%= if (current_route.PathName == "resumePath") { %> menu__item--active<% } %>">Resume</div></a>
    <a href="/contact"><div class="menu__item<%= if (current_route.PathName == "contactPath") { %> menu__item--active<% } %>">Contact</div></a>
</nav>

To make it look slightly simpler, we can add a custom helper fuction in actions/render.go:

// actions/render.go
    ...
    func init() {
        r = render.New(render.Options{
            ...
            Helpers: render.Helpers{
                "isCurrentPathName": func(current buffalo.RouteInfo, name string) bool {
                    return current.PathName == name
            },
            },
        })
    }

Our updated menu looks like this:

// templates/_menu.html
<nav class="menu">
    <a href="/"><div class="menu__item<%= if (isCurrentPathName(current_route, "rootPath"))   { %> menu__item--active<% } %>">Home</div></a>
    <a href="/resume"><div class="menu__item<%= if (isCurrentPathName(current_route, "resumePath")) { %> menu__item--active<% } %>">Resume</div></a>
    <a href="/contact"><div class="menu__item<%= if (isCurrentPathName(current_route, "contactPath")) { %> menu__item--active<% } %>">Contact</div></a>
</nav>

And the final result might be similar to:

The full source code of this example is available on Github.