Building a RESTful API with revel.go

revel.go is also a fully-fledged web framework like Python's Django. It is older than Gin and is termed as a highly productive web framework. It is an asynchronous, modular, and stateless framework. Unlike the go-restful and Gin frameworks where we created the project ourselves, Revel generates a scaffold for working directly:

  1. Install revel.go using the following command:
go get github.com/revel/revel  
  1. In order to run the scaffold tool, we should install one more supplementary package:
go get github.com/revel/cmd/revel

Make sure that $GOPATH/bin is in your PATH variable. Some external packages install the binary in the $GOPATH/bin directory. If it is in the path, we can access the executables system wide. Here, Revel installs a binary called revel.

  1. On Ubuntu or Mac OS X, you can make sure to point Go binaries to the system path using this command:
export PATH=$PATH:$GOPATH/bin

Add this export statement to ~/.bashrc to save the setting permanently. On Windows, you have to directly call the executable by its location. Now we are ready to go with Revel.

  1. Let's create a new project called railAPIRevel in github.com/git-user/chapter4:
revel new railAPIRevel
  1. This creates a project scaffold without writing a single line of code. This is how web frameworks abstract things for quick prototyping. A Revel project layout tree looks like this:
    conf/             Configuration directory
app.conf Main app configuration file
routes Routes definition file

app/ App sources
init.go Interceptor registration
controllers/ App controllers go here
views/ Templates directory

messages/ Message files

public/ Public static assets
css/ CSS files
js/ Javascript files
images/ Image files

tests/ Test suites

Out of all those boilerplate directories, three things are important for creating an API. Those are:

Controllers are the logic containers that execute the API logic. The app.conf file allows us to set the host, port, dev mode/production mode, and so on. routes defines the triple of the endpoint, REST verb, and function handler (here, controller's function). This is required for combining routes, verbs, and function handlers.

Let's use the same Rail API example we developed with go-restful and Gin. However, here, due to the redundancy, we drop the database logic. We will see shortly how to build GET, POST, and DELETE actions for the API using Revel:

  1. Now, modify the routes file to this:
# Routes Config
#
# This file defines all application routes (Higher priority routes
first)

#

module:testrunner
# module:jobs


GET /v1/trains/:train-id App.GetTrain
POST /v1/trains App.CreateTrain
DELETE /v1/trains/:train-id App.RemoveTrain

The syntax may look a bit new. It is a configuration file where we simply define a route in this format:

VERB       END_POINT         HANDLER

VERB is a REST verb, END_POINT is the API endpoint, and HANDLER is the name of the function that processes requests.

We haven't defined handlers yet. In the endpoint, the path parameters are accessed using the :param notation. This means for a GET request to the server, train-id will be passed as the path parameter.

  1. Now, navigate to the controllers folder and modify the existing controller in the app.go file.
  2. We first create a struct that represents our application context. let's name it App. We should also define another struct for TrainResource that holds rail information:
type App struct {
*revel.Controller
}
// TrainResource is the model for holding rail information
type TrainResource struct {
ID int `json:"id"`
DriverName string `json:"driver_name"`
OperatingStatus bool `json:"operating_status"`
}
  1. Now let's define CRUD handlers in Revel. First is GetTrain. Why a capital lettered name for a controller? Because Revel expects controllers to be exported out of the package. Go packages only export names starting with capital letters. The controller accesses the path parameter to get a train ID and uses it to query the database. Here we are mocking the database result for brevity:
// GetTrain handles GET on train resource
func (c App) GetTrain() revel.Result {
var train TrainResource
// Getting the values from path parameters.
id := c.Params.Route.Get("train-id")
// use this ID to query from database and fill train table....
train.ID, _ = strconv.Atoi(id)
train.DriverName = "Logan" // Comes from DB
train.OperatingStatus = true // Comes from DB
c.Response.Status = http.StatusOK
return c.RenderJSON(train)
}
  1. In CreateTrain, we add the POST request logic. We should create an object of TrainResource struct and pass it to a function called c.Params.BindJSON. JSON tags('json:"id"`) gives us the flexibility of defining output fields. This is a good practice in Go while working with JSON. Then, we return an HTTP response with 201 created status. We can use the RenderJSON method on context to marshal a struct to JSON on the fly:
// CreateTrain handles POST on train resource
func (c App) CreateTrain() revel.Result {
var train TrainResource
c.Params.BindJSON(&train)
// Use train.DriverName and train.OperatingStatus
// to insert into train table....
train.ID = 2
c.Response.Status = http.StatusCreated
return c.RenderJSON(train)
}

  1. The RemoveTrain handler logic is similar to that of GET. A subtle difference is that nothing is sent in the body. As we previously mentioned, database CRUD logic is omitted from the preceding example. It is an exercise for readers to try adding SQLite3 logic by observing what we have done in the go-restful and Gin sections:
// RemoveTrain implements DELETE on train resource
func (c App) RemoveTrain() revel.Result {
id := c.Params.Route.Get("train-id")
// Use ID to delete record from train table....
log.Println("Successfully deleted the resource:", id)
c.Response.Status = http.StatusOK
return c.RenderText("")
}
  1. Finally, the default port on which the Revel server runs is 9000. The configuration to change the port number is in the conf/app.conf file. Let's follow the tradition of running our app on 8000. So, modify the HTTP port section of app.conf to the following. This tells the Revel server to run on a different port:
......
# The IP address on which to listen.
http.addr =

# The port on which to listen.
http.port = 8000 # Change from 9000 to 8000 or any port

# Whether to use SSL or not.
http.ssl = false
......
  1. Now, we can run our Revel API server using this command:
revel run github.com/git-user/chapter4/railAPIRevel
  1. Our app server starts at http://localhost:8000. Now, let's make a few API requests:
curl -X GET "http://localhost:8000/v1/trains/1"

Output
=======
{
"id": 1,
"driver_name": "Logan",
"operating_status": true
}

POST request:

curl -X POST \
http://localhost:8000/v1/trains \
-H 'cache-control: no-cache' \
-H 'content-type: application/json' \
-d '{"driver_name":"Magneto", "operating_status": true}'

Output
======
{
"id": 2,
"driver_name": "Magneto",
"operating_status": true
}

DELETE is the same as GET, but no body is returned. Here, the code is illustrated to show how to handle the request and response. Remember, Revel is more than a simple API framework. It is a fully-fledged web framework similar to Django (Python) or Ruby on Rails. We have got templates, tests, and many more bundled in revel.go. It is mainly used for web development, but one can also use it to quickly develop a REST API.

Make sure that you create a new Revel project for GOPATH/user, otherwise, your Revel command-line tool may not find the project while running the project.

There is middleware support in all the web frameworks we saw in this chapter. go-restful names its middleware Filters, whereas Gin names them Custom Middleware, and Revel calls its middleware, Interceptors. A middleware reads or writes the request and response before and after a function handler, respectively.

In Chapter 3, Working with Middleware and RPC, we have already briefly discussed middleware.