DEV Community

Cover image for Go for the REST
Jan Stamer
Jan Stamer

Posted on • Edited on

Go for the REST

The Go programming language is a great fit for RESTful web applications. Picking the right Go web framework to start with is not an easy task. Lucky enough it is a task solved already. Pick the Go web framework buffalo and off you go. Not convinced yet? Learn how to build a RESTful web application with buffalo.

Buffalo is a whole ecosystem to develop web applications in Go. Buffalo combines routing, templating and testing to build web applications based on the famous model-view-controller pattern. The foundation is a powerful tooling with hot reload, a generator for views and controller and tools for common tasks like compressing JavaScript or database migration. Buffalo combines existing libraries like the Gorilla web toolkit with best practices of Go web development. With buffalo comes a uniform and consistent project structure covering packages and directories up until handling the database session. So let's get started.

Installing Buffalo

We'll use the new Go modules for dependency management. So let's activate that using the environment variable GO111MODULE=on. Now we can install buffalo with go get -u -v github.com/gobuffalo/buffalo/buffalo. Executing the command buffalo in your shell should display the help.

Create and run the RESTful web application

Now we create our first buffalo RESTful web application named rowdy. The command buffalo new rowdy --api creates a new RESTful buffalo web application (without frontend) in the directory rowdy. We can run the application with the command buffalo dev (from directory rowdy). The application is available at http://localhost:3000. But wait, that doesn't work! The generated application needs a running database. so we start a PostgreSQL database with docker run --name rowdy_db -e POSTGRES_PASSWORD=postgres -d postgres. Now you should be able to start the application and access the REST API under the web root saying "Welcome to Buffalo".

You can keep the application running while you develop. Buffalo will automatically pick up changes in sources and rebuild and restart the application as needed.

Guide through the App

Entry point of the buffalo application is the main.go in the root directory of our application. The main.go is the main package of the application and starts the application by executing the method Serve of the buffalo struct buffalo.App. The buffalo app itself is created in the app.go which is in the package actions.

The app.go is where the music plays. In the app.go all routes and middlewares are registered. For every route an action is registered. An action is the controller layer of the MVC pattern. For the moment we only have one action which is the HomeHandler. With app.GET("/", HomeHandler) the action HomeHandler is registered in the app.go for the path /. The HomeHandler itself is declared in home.go in the package actions, and the associated test is in home_test.go.

The package model is for the model layer of MVC. Buffalo assumes the model layer is the persistence layer of the application running against a relational database like postgres. This model layer is based on buffalo pop, a active record inspired persistence layer for Go.

The directory migrations is all about maintaining the schema of the relational database. Here scripts for the database migration framework fizz are stored. But we'll get to that later.

Then there are the directories fixtures, config and grifts. Fixtures are scripts to create database content for tests. Config is for the configuration of the buffalo code generator. Grifts are small scripts to automate common tasks like listing all routes.

To complete our guide we look at the generated configuration files. The most important config file of our application is the database.yml with the database credentials for several environments. Then there is the Dockerfile to create a docker container of our app.

Create a Health Check Action

Now let's create our first action, a health check at route /health/check. We generate the skeleton of our health check action using the buffalo code generator by running buffalo generate action health check --skip-template. That generated an action with name HealthCheck. The first parameter of the generator command is the name of the action and the second parameter check is the name of the handler. Since we build a RESTful API we don't need a HTML template for our action and suppress generating one with the parameter --skip-template. Buffalo created the files health.go and health_test.go in the directory actions. The generator also registered our new action for the path /health/check in the app.go.

A buffalo action needs to implement the interface buffalo.Handler. The terms action and handler are both synonymously used for the controller layer of the MVC pattern. A look at the home.go shows the generated code:

func HealthCheck(c buffalo.Context) error {
    return c.Render(200, r.HTML("health/check.html"))
}
Enter fullscreen mode Exit fullscreen mode

The generated handler tries to render the template health/check.html as HTML. Since we build a RESTful API we don't have templates so this can't work. We change the implementation to c.Render(200, r.String("Up and running!")) so that it returns a plain text string.

func HealthCheck(c buffalo.Context) error {
    return c.Render(200, r.String("Up and running!"))
}
Enter fullscreen mode Exit fullscreen mode

Test the Health Check

Now we need to fix the test for our health check. The whole buffalo application is available it the test. In the test you can run requests against the application or even access the database it's running against of the HTTP session.

To test the health check we run request path /health/check and verify the HTTP status code. The code to do that is:

package actions

func (as *ActionSuite) Test_Health_Check() {
    result := as.HTML("/health").Get()

    as.Equal(200, result.Code)
    as.Contains(result.Body.String(), "Up and running")
}
Enter fullscreen mode Exit fullscreen mode

More about Actions and Routes

When configuring routes you can use placeholders and regular expressions in the path like app.GET("/account/{category}/{id:[0-9]+}", AccountHandler). Since that's base on the Gorilla web toolkit you can make full use of the Gorilla multiplexer.

In the handler itself you have full access to the buffalo.Context. With the buffalo context you can render JSON or HTML and access path and query parameters with them methods c.Params() and c.Param(string). You can also acess the logger and log health check requests with c.Logger().Info("Health checked by: %v", c.Request().Host).

To get an overview of all registered routes use the command buffalo routes which prints:

METHOD | PATH           | ALIASES | NAME            | HANDLER
------ | ----           | ------- | ----            | -------
GET    | /              |         | rootPath        | rowdy/actions.HomeHandler
GET    | /health/check/ |         | healthCheckPath | rowdy/actions.HealthCheck
Enter fullscreen mode Exit fullscreen mode

CRUD Resources

To make it easy to develop CRUD functionality buffalo has resources. A buffalo resource always contains the actions Create, Show, Update, Destroy and List with standardized paths in the router. Resources are always RESTful and never use templates.

Now we create a resource to manage concerts with buffalo generate resource concert. Buffalo created several files for us. The implemntation of the concert resource in the files concert.go in the package actions, along with the tests in concert_test.go. A database mapping for pop in concert.go in the package models including test. And last but not least in the directory migrations two fizz scripts to create a table for the concerts in our database schema. The concerts resources is registered in the app.go using app.Resource("/concerts", ConcertsResource{}).

Looking at the output of buffalo routes we see all the routes of our new concert resource.

METHOD | PATH                    | ALIASES | NAME            | HANDLER
------ | ----                    | ------- | ----            | -------
GET    | /                       |         | rootPath        | rowdy/actions.HomeHandler
GET    | /concerts/              |         | concertsPath    | rowdy/actions.ConcertsResource.List
POST   | /concerts/              |         | concertsPath    | rowdy/actions.ConcertsResource.Create
GET    | /concerts/{concert_id}/ |         | concertPath     | rowdy/actions.ConcertsResource.Show
PUT    | /concerts/{concert_id}/ |         | concertPath     | rowdy/actions.ConcertsResource.Update
DELETE | /concerts/{concert_id}/ |         | concertPath     | rowdy/actions.ConcertsResource.Destroy
GET    | /health/check/          |         | healthCheckPath | rowdy/actions.HealthCheck
Enter fullscreen mode Exit fullscreen mode

Let's now look into the generated handler method Show. We see that buffalo gets the database connection from the buffalo context with c.Value("tx").(*pop.Connection). Then buffalo loads the concert model from the database using pop. The id of the concert model is provided as a path parameter. Finally the loaded concert model is returned with c.Render(200, r.Auto(c, concert)). Depending on the negotiated content-type this is either as JSON or as XML.

// GET /concerts/{concert_id}
func (v ConcertsResource) Show(c buffalo.Context) error {
    // Get the DB connection from the context
    tx, ok := c.Value("tx").(*pop.Connection)
    if !ok {
        return errors.WithStack(errors.New("no transaction found"))
    }

    // Allocate an empty Concert
    concert := &models.Concert{}

    // To find the Concert the parameter concert_id is used.
    if err := tx.Find(concert, c.Param("concert_id")); err != nil {
        return c.Error(404, err)
    }

    return c.Render(200, r.Auto(c, concert))
}
Enter fullscreen mode Exit fullscreen mode

Error Handling

Handling errors properly can be tricky. But buffalo has you covered. Errors generally occur in handlers. Handlers have two options of dealing with them:

  1. Return a normal Go error from the handler like errors.New("Boom!"). The application then returns HTTP status code 500 for internal server error.

  2. Use the bufallo context method Error to handle like return context.Error(501, errors.New("That blew up!")). That way the applications returns the custom status code 501.

Either way the application keeps running and the error is logged and returned either as JSON or XML. If buffalo runs in dev mode (environment variable GO_ENV="development" or start with buffalo dev) this will contain a stacktrace of the error.

Middlewares

Buffalo applications feature many best practices in Go web development. This is mainly featured by various middleware. A middleware is code that runs befor or after a HTTP handler (in buffalo called action). Middlewares are a general concept of Go web development. A good read about the concept of middleware is Making and Using HTTP Middleware by Alex Edwards.

Buffalo comes with lots of usefule middlewares integrated into the buffalo.App right from the start. E.g. middleware that prevents your app from crashing after a panic or an error. In our generated app middleware is registered in actions/app.go. One example is app.Use(forceSSL) which registers a middleware that redirects HTTP requests to HTTPS.

It's also possible to skip middleware for specific routes. We could use this to skip authentication middleware for our health check route. The command buffalo task middleware shows the registered middlewares for every route.

Configuration

A buffalo app is configured using environment variables as proposed in the twelve-factor app. The configuration is powered by the buffalop package envy.

There are predefined environment variables like GO_ENV for the current environment (e.g. development, test, production) or HOST (localhost) and PORT (3000). You can also use custom variables. You can use an optional environment variable with envy.Get("CUSTOM_SETTING", "default-value"). Or a mandatory variable with envy.MustGet("MY_REQUIRED_OPTION") which results in an error if the variable is not set.

You can also read environment variables from the file .env. Buffalo looks for a file named .env in the current directory and evaluates it. This nicely integrates with direnv if you create a .envrc with only the line dotenv (see .env files). You can use this to have buffalo read your local development settings which of course you should never check in.

Plugins

Buffalo can be extended using plugins. The database framework pop is implemented as a buffalo plugin. There are also plugins for kubernetes or Azure. The buffalo website contains a list of available plugins.

Build the App

To build the app run buffalo build. Buffalo compiles the app and creates a standalone binary rowdy in the directory build.

Wrap up the REST

We learned how to build a RESTful application with the buffalo framework that adheres to the MVC pattern. The model layer consists of structs mapped against a PostgreSQL database using pop. Actions and resources make up the controller layer. We created a simple health check action and a CRUD resource for concerts. We have not used the view layer yet since all we need is serializing structs to XML or JSON. We learned how to handle error, how to deal with configuration and how to run a production build in the CI environment.

Buffalo comes with a uniform and consistent structure for web applications in go, follows best practices of Go web development and covers the full lifecycle from development to production.

Yet there's still so much that we could't cover. Buffalo has a lot to offer to build frontends. Or you can use buffalos tasks or background workers. All this is covered by the great buffalo documentation.

And of course stay tuned because I'll spread more buffalo love here soon!

Top comments (4)

Collapse
 
shiraazm profile image
Shiraaz Moollatjie

Why would one use buffalo instead of the standard library or gorilla?

Collapse
 
finsterwalder profile image
Malte Finsterwalder

Your first code for "func HealthCheck" is the same as the second one. That does not match your text.

Collapse
 
remast profile image
Jan Stamer

Fixed it, thanks!

Collapse
 
remast profile image
Jan Stamer • Edited

Buffalo adds developer tooling, a uniform project structure and lots of best practices. And buffalo makes a lot of small libraries work smoothly together. You can mix and match what you need.