Show Navigation

Using HAL with JSON Views

Learn to build discoverable APIs with Grails

Authors: ZacharyKlein

Grails Version: 5.0.1

1 Grails Training

Grails Training - Developed and delivered by the folks who created and actively maintain the Grails framework!.

2 Getting Started

In this guide you are going to explore HAL (Hypertext Application Language) support in Grails, using JSON views (provided by the Grails Views library).

Hypertext Application Language (HAL) is an Internet Draft (a "work in progress") standard convention for defining hypermedia such as links to external resources within JSON or XML code.

— Wikipedia

The purpose of HAL is make APIs "discoverable" - it defines a set of conventions that allow consumers of your API to follow links between resources, as well as providing pagination and other convenience features for "exploring" an API. HAL is a popular standard for implementing HATEOAS (Hypermedia As The Engine Of Application State) architecture, which is an extension of basic REST architecture.

HAL Resource Structure
Figure 1. HAL Resource Structure, from http://stateless.co
For a thorough introduction to HAL, check out the overview and specification at the following URL: http://stateless.co/hal_specification.html

Grails provides support for HAL through JSON views, which are part of the Grails Views library. You can use this library in an existing application following the installation steps in the documentation, or you can create a new application using either the rest-api profile or one of the frontend profiles (angular, angular2 and react) which extend the rest-api profile.

In this guide, we have provided a basic Grails 5.0.1 application using the rest-api profile in the initial project. We have also included a few domain classes to expose via our API. You may generate your own project if you wish (in which case you will need to copy the domain classes from initial/grails-app/domain/ into your own project), or simply use the initial project to follow along with the guide.

2.1 What You will Need

To complete this guide, you will need the following:

  • Some time on your hands

  • A decent text editor or IDE

  • JDK 1.8 or greater installed with JAVA_HOME configured appropriately

2.2 How to Complete the Guide

To get started do the following:

or

The Grails guides repositories contain two folders:

  • initial Initial project. Often a simple Grails app with some additional code to give you a head-start.

  • complete A completed example. It is the result of working through the steps presented by the guide and applying those changes to the initial folder.

To complete the guide, go to the initial folder

  • cd into grails-guides/using-hal-with-json-views/initial

and follow the instructions in the next sections.

You can go right to the completed example if you cd into grails-guides/using-hal-with-json-views/complete

3 Running the Application

Grails provides excellent support for RESTful url mappings to expose domain resources. In the initial project, we’ve already annotated our domain classes with the @Resource annotation, which generates a RestfulController and associated URL mappings to expose each domain class as a restful resource.

grails-app/domain/com/example/Customer.groovy
package com.example

import grails.rest.*

@Resource(readOnly = true, uri='/api/customers') (1)
class Customer {
1 The @Resource annotation takes several optional arguments, including a URL endpoint and format options, as well as whether the API should only expose read endpoints

Adding these annotations to our domain classes gives us a running start to creating our API. See the Grails documentation for more information on @Resource

To run the application use the ./gradlew bootRun command which will start the application on port 8080.

Now that the Grails app is running, we can try out the API that Grails generated for us via the @Resource annotation. Make a GET request to /api/products to retrieve a list of products:

curl -H "Accept: application/json" localhost:8080/api/products
'''
[
  {
    "id": 1,
    "category": {
      "id": 1
    },
    "inventoryId": "CLOTH001",
    "name": "Cargo Pants",
    "price": 15.00
  },
  {
    "id": 2,
    "category": {
      "id": 1
    },
    "inventoryId": "CLOTH002",
    "name": "Sweater",
    "price": 12.00
  },
  {
    "id": 3,
    "category": {
      "id": 1
    },
    "inventoryId": "CLOTH003",
    "name": "Jeans",
    "price": 15.00
  },
  {
    "id": 4,
    "category": {
      "id": 1
    },
    "inventoryId": "CLOTH004",
    "name": "Blouse",
    "price": 18.00
  },
  {
    "id": 5,
    "category": {
      "id": 1
    },
    "inventoryId": "CLOTH005",
    "name": "T-Shirt",
    "price": 10.00
  },
  {
    "id": 6,
    "category": {
      "id": 1
    },
    "inventoryId": "CLOTH006",
    "name": "Jacket",
    "price": 20.00
  },
  {
    "id": 7,
    "category": {
      "id": 2
    },
    "inventoryId": "FURN001",
    "name": "Bookcase",
    "price": 40.00
  },
  {
    "id": 8,
    "category": {
      "id": 2
    },
    "inventoryId": "FURN002",
    "name": "Coffee Table",
    "price": 50.00
  },
  {
    "id": 9,
    "category": {
      "id": 2
    },
    "inventoryId": "FURN003",
    "name": "Vanity",
    "price": 90.00
  },
  {
    "id": 10,
    "category": {
      "id": 3
    },
    "inventoryId": "TOOL001",
    "name": "Table Saw",
    "price": 120.00
  }
]
'''
//...
]

Here, a GET request to /api/orders/1 returns the Order with an id of 1:

curl -H "Accept: application/json" localhost:8080/api/orders/1
'''
{
   "id" : 1,
   "shippingAddress" : {
      "id" : 1
   },
   "shippingCost" : 13.54,
   "orderId" : "0A12321",
   "orderPlaced" : "2017-02-08T09:10:36Z",
   "products" : [
      {
         "id" : 11
      },
      {
         "id" : 6
      },
      {
         "id" : 1
      }
   ],
   "customer" : {
      "id" : 1
   }
}
'''
Because we’ve specified readOnly = true in our @Resource annotations, Grails will not generate endpoints for update/create/delete operations. This is sufficient for the steps in this guide, but you can remove the readOnly property (or set it to false) to enable the "write" operations.

4 Building our API

The default JSON rendered by Grails is a good start, but it doesn’t necessarily express the details we want in our public-facing API. JSON Views allow us to render our data using Groovy’s StreamingJsonBuilder, in a statically-compiled Groovy view. JSON views provide a powerful DSL-based tool for expressing the JSON output from our API.

4.1 Introducing JSON Views

Here’s a trivial example of a JSON view:

json.message {
    hello "world"
}

This JSON view would produce the following ouput when rendered:

{"message":{ "hello":"world"}}

JSON views also support a model which references the data passed in to the view, as seen below:

model {
    String message
}
json.message {
    hello message
}

JSON views are Groovy files with the file extension gson, and they reside in the grails-app/views directory, just like GSP views. They are resolved (by convention) to a controller with the same name as the view directory, again like GSP views.

4.2 Customizing our API

Let’s create a JSON view to customize the output from the /api/orders/$id endpoint. Right now, the default JSON renderer includes ids of all associated objects. However, we don’t want to expose the id of the shippingAddress property (which is an instance of our Address domain class) - it’s not exposed as a domain resource in our API and it’s only relevant to users of API as part of an Order or Customer. Ideally, we’d like to include the shippingAddress fields in the JSON output of our Order API.

In addition, we’d like to express the orderId property as the order’s id, rather than the actual id from the database.

Create a new directory under grails-app/views, called order:

$ mkdir grails-app/views/order
If you’re familiar with Grails' view resolution, you may be thinking we need to create an OrderController in order to use views from our order directory. We could do that, however because we’re making use of the @Resource annotation on our domain class, Grails will generate an associated OrderController (which will in turn inherit from RestfulController) for us. So at this point, we don’t need to create a controller for our Order class.

Create a new JSON view called show.gson. This will resolve to the show action in our controller, just as a show.gsp page would in a normal Grails application. Edit the new view with the following content:

grails-app/views/order/show.gson
import com.example.Order

import java.text.SimpleDateFormat

SimpleDateFormat simpleDateFormat = new SimpleDateFormat('M-dd-yyy')

model {
    Order order
}
json {
    id order.orderId
    shippingCost order.shippingCost
    date simpleDateFormat.format(order.orderPlaced)

    shippingAddress { (1)
        street order.shippingAddress.street
        street2 order.shippingAddress.street2
        city order.shippingAddress.city
        state order.shippingAddress.state
        zip order.shippingAddress.zip
    }

    products order.products.collect { [id: it.id] } (2)

    customer {
        id order.customer.id
    }
}
1 Here we’re filling out our shippingAddress with the fields from the Address class
2 Notice that we’re iterating over a collect (order.products) with the collect method and returning a map - this will create a JSON array of objects

Now if you make a request to /api/orders/1, you should see the following output:

curl -H "Accept: application/json" localhost:8080/api/orders/1
{
    id: "0A12321",
    shippingCost: 13.54,
    date: "2-08-2017",
    shippingAddress: {
        street: "321 Arrow Ln",
        street2: null,
        city: "Chicago",
        state: "IL",
        zip: 646465
    },
    products: [
            {
                id: 11
            },
            {
                id: 1
            },
            {
                id: 6
            }
    ],
    customer: {
        id: 1
    }
}

Let’s create another JSON view for our Customer domain class. Create a new directory under grails-app/views, called customer, and a new JSON view show.gson

$ mkdir grails-app/views/customer

Create a new JSON view called show.gson. This will resolve to the show action in our controller, just as a show.gsp page would in a normal Grails application. Edit the new view with the following content:

grails-app/views/customer/show.gson
import com.example.Customer

model {
    Customer customer
}
json {
    id customer.id
    firstName customer.firstName
    lastName customer.lastName
    fullName "${customer.firstName} ${customer.lastName}"

    address {
        street customer.address.street
        street2 customer.address.street2
        city customer.address.city
        state customer.address.state
        zip customer.address.zip
    }

    orders customer.orders.collect { [id: it.id] }
}

5 Make our API Discoverable via HAL

We’ve customized our API somewhat now, but we have a few issues:

  1. By customizing our id property for orders, it’s no longer clear to clients how to reference a particulate record (since our API still relies on the database id)

  2. Our three exposed domain classes have a number of associations where only ids are exposed - this means that clients need to make a new request to get the details on an associated object (e.g, a client consuming an order would need to make separate requests to obtain the fields for its products)

  3. Our API is rather opaque - without documentation, users of our API would have to guess the endpoints to arrive at associated records. Even with documentation, clients would likely need to use custom code to navigate our API, without a consistent standard to follow.

The conventions of the HAL+JSON standard can help us solve these issues, and JSON views provide first-class support for HAL - let’s see how we can use it.

Links are the key in the HAL standard. HAL resources include a special field called _links, which contains an array of JSON objects that define links to related resources. A HAL link contains (at least) two pieces of information - a relationship, and an href containing the URL to access the related resource. Other metadata can be included as well.

Here’s a sample of a JSON body with a _links field, and two links:

{
    title: "Groovy Recipes",
    author: "Scott Davis",
    pages: 100,

    "_links": {
        "self": {
            "href": "http://localhost:8080/book/show/1",    (1)
        },
        "author": {
            "href": "http://localhost:8080/author/show/1", (2)
        }
    }
}
1 self is a special link that every HAL resource should include - it specifies the URL "back" to the current resource.
2 Here we’re defining a custom link called author that specifies the URL for the author field

When a HAL resource is accessed by a client, it is possible to "browse" the relationships expressed in the _links field without the client knowing the exact makeup of the API endpoint.

//examples are using the fetch API: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API

//retrieve a book instance from the API
fetch("http://localhost:8080/api/book/1").then(function(response) {
    return response.json();
}).then(function(data) {
    this.book = data;
});

//retrieve book's author using links
var author;
fetch(book._links.author.href).then(function(response) {
    return response.json();
}).then(function(data) {
    author = data;
});

JSON views implement a Groovy trait HalView, which exposes a hal helper with several methods for outputting HAL-compliant JSON. One of those is the links method:

model {
    Order order
}
json {
    hal.links(order)
    //...
}

Calling hal.links() on our domain resource produces the following JSON output:

{
    _links: {
        self: {
            href: "http://localhost:8080/api/orders/1",
            hreflang: "en_US",
            type: "application/hal+json"
        }
    }
}

Let’s edit our order/show.gson view to include a self link as well as a link to the associated customer:

grails-app/views/order/show.gson
import com.example.Order

import java.text.SimpleDateFormat

SimpleDateFormat simpleDateFormat = new SimpleDateFormat('M-dd-yyy')

model {
    Order order
}
json {
    hal.links(self: order, customer: order.customer) (1)

    id order.orderId
    shippingCost order.shippingCost
    date simpleDateFormat.format(order.orderPlaced)

    shippingAddress {
        street order.shippingAddress.street
        street2 order.shippingAddress.street2
        city order.shippingAddress.city
        state order.shippingAddress.state
        zip order.shippingAddress.zip
    }

    products order.products.collect { [id: it.id] }

    customer {
        id order.customer.id
    }
}
1 The links method can take a domain resource instance, or a map of link names and objects to link.
{
    _links: {
        self: {
            href: "http://localhost:${serverPort}/api/orders?id=1",
            hreflang: "en",
            type: "application/hal+json"
        },
        customer: {
            href: "http://localhost:${serverPort}/api/customers?id=1",
            hreflang: "en",
            type: "application/hal+json"
        }
    },
    id: "0A12321",
    shippingCost: 13.54,
    date: "2-08-2017",
    shippingAddress: {
        street: "321 Arrow Ln",
        street2: null,
        city: "Chicago",
        state: "IL",
        zip: 646465
    },
    products: [
            {
                id: 11
            },
            {
                id: 1
            },
            {
                id: 6
            }
    ],
    customer: {
        id: 1
    }
}
    //...
}

5.2 Render Collections with Templates

Order includes a one-many relationship with Product, and right now our API returns a simple list of ids to represent products in an order. Ideally we’d like to include links to these products as well, so that clients of our API can retrieve the details of each product by following the links we provide. We can use JSON views' template functionality.

In the grails-app/views/order directory, create a new JSON template with the name _product.gson:

grails-app/views/order/_product.gson
import com.example.Product

model {
  Product product
}
json {
  hal.links(product)

  id product.id
}

Now in our order/show.gson view, we can pass order.products to the tmpl helper, using our new _product template:

grails-app/views/order/show_v3.gson
import com.example.Order

import java.text.SimpleDateFormat

SimpleDateFormat simpleDateFormat = new SimpleDateFormat('M-dd-yyy')

model {
    Order order
}
json {
    hal.links(self: order, customer: order.customer)

    id order.orderId
    shippingCost order.shippingCost
    date simpleDateFormat.format(order.orderPlaced)

    shippingAddress {
        street order.shippingAddress.street
        street2 order.shippingAddress.street2
        city order.shippingAddress.city
        state order.shippingAddress.state
        zip order.shippingAddress.zip
    }

    products tmpl.product(order.products) (1)
}
1 The tmpl helper will resolve a method name to a template in the current view directory - e.g, tmpl.product will resolve to /order/_product.gson. If you want to access a template from outside the current directory, you can use an absolute path (relative to the views directory) as a string: tmpl."/customer/order"() will resolve to grails-app/views/customer/_order.gson.
For more information on using templates in JSON views, see the Grails Views documentation.

Make a request to http://localhost:8080/api/orders/1, and you should see _links for each product in the products array:

{
    _links: {
        self: {
            href: "http://localhost:${serverPort}/api/orders?id=1",
            hreflang: "en",
            type: "application/hal+json"
        },
        customer: {
            href: "http://localhost:${serverPort}/api/customers?id=1",
            hreflang: "en",
            type: "application/hal+json"
        }
    },
    id: "0A12321",
    shippingCost: 13.54,
    date: "2-08-2017",
    shippingAddress: {
        street: "321 Arrow Ln",
        street2: null,
        city: "Chicago",
        state: "IL",
        zip: 646465
    },
    products: [
        {
            _links: {
                self: {
                    href: "http://localhost:${serverPort}/api/products/11",
                    hreflang: "en",
                    type: "application/hal+json"
                }
            },
            id: 11
        },
        {
            _links: {
                self: {
                    href: "http://localhost:${serverPort}/api/products/6",
                    hreflang: "en",
                    type: "application/hal+json"
                }
            },
            id: 6
        },
        {
            _links: {
                self: {
                    href: "http://localhost:${serverPort}/api/products/1",
                    hreflang: "en",
                    type: "application/hal+json"
                }
            },
            id: 1
        }
    ]
}

Let’s use the same technique for the Customer’s orders - create a new template at `grails-app/views/customer/_order.gson:

grails-app/views/customer/_order.gson
import com.example.Order

model {
    Order order
}
json {
    hal.links(order)

    id order.id
}

Edit the customer/show.gson view to use the new _order template:

Let’s use the same technique for the Customer’s orders - create a new template at `grails-app/views/customer/_order.gson:

grails-app/views/customer/show.gson
import com.example.Customer

model {
    Customer customer
}
json {
    id customer.id
    firstName customer.firstName
    lastName customer.lastName
    fullName "${customer.firstName} ${customer.lastName}"

    address {
        street customer.address.street
        street2 customer.address.street2
        city customer.address.city
        state customer.address.state
        zip customer.address.zip
    }

    orders tmpl.order(customer.orders)
}

5.3 Embed Associated Objects

Let’s take a look at our Product domain resource. Product has a belongsTo relationship with Category, which is expressed in our default JSON output in a simple object with the category id:

{
    id: 1,
    category: {
        id: 1
    },
    inventoryId: "CLOTH001",
    name: "Cargo Pants",
    price: 15
}

Again we’d like to make it easier for consumers of the API to obtain the category for the product. We have several options:

  1. We could simply include the category details in our JSON view: This approach obscures the boundary between the Product and Category resources in our API - it gives the (incorrect) impression to the client that category.name is a property of Product, rather than a API resource in its own right.

  2. We could provide a link to the category: This approach would require clients to make a new request to get the category details, and it’s likely that most clients will want both the product and the category details in the same request.

You may recall that in the case of an order’s shippingAddress, we used the first of these two approaches (including the associated object’s details in the JSON view) - this is because Address is not exposed in our API as a resource, so an Address is effectively part of either an Order (shippingAddress) or a Customer (address) as far as our API is concerned.

HAL specifies an _embedded property to represent cross-resource relationships in a nested format. With an embedded approach, we could include the Category in the same HAL+JSON response, but the category would be in a separate element to make it clear that these are separate resources.

JSON views provide an embedded method (via the hal helper) that will generate an _embedded element in our JSON view. It will include the default JSON output for the embedded objects as well as a _self link for each object. Let’s use this to embed the category in our product output.

Create a new directory under grails-app/views, called product:

$ mkdir grails-app/views/product/

Create a new JSON view under this directory, called show.gson:

grails-app/views/product/show.gson
import com.example.Product

model {
    Product product
}
json {
    hal.links(product)
    hal.embedded(category: product.category) (1)

    id product.inventoryId
    name product.name
    price product.price
}
1 We’re passing embedded method a map of element names (in this case category) to objects to embed (product.category)

Now make a request to http://localhost:8080/api/products/1:

{
  "_links": {
    "self": {
      "href": "http://localhost:${serverPort}/api/products/1",
      "hreflang": "en",
      "type": "application/hal+json"
    }
  },
  "_embedded": {
    "category": {
      "_links": {
        "self": {
          "href": "http://localhost:${serverPort}/api/categories/1",
          "hreflang": "en",
          "type": "application/hal+json"
        }
      },
      "name": "Clothing",
      "version": 0
    }
  },
  "id": "CLOTH001",
  "name": "Cargo Pants",
  "price": 15.00
}

In previous code snippet, the category object includes the default JSON renderer output for our Category resource, as well as a _self link so clients can request the category directly if needed.

Let’s use the embedded method on the `order/show.gson view, and embed the order.customer resource:

grails-app/views/order/show.gson
import com.example.Order

import java.text.SimpleDateFormat

SimpleDateFormat simpleDateFormat = new SimpleDateFormat('M-dd-yyy')

model {
    Order order
}
json {
    hal.links(self: order, customer: order.customer)
    hal.embedded(customer: order.customer) (1)

    id order.orderId
    shippingCost order.shippingCost
    date simpleDateFormat.format(order.orderPlaced)

    shippingAddress {
        street order.shippingAddress.street
        street2 order.shippingAddress.street2
        city order.shippingAddress.city
        state order.shippingAddress.state
        zip order.shippingAddress.zip
    }

    products tmpl.product(order.products)
}
      "href": "http://localhost:${serverPort}/api/orders?id=1",
      "hreflang": "en",
      "type": "application/hal+json"
    },
    "customer": {
      "href": "http://localhost:${serverPort}/api/customers?id=1",
      "hreflang": "en",
      "type": "application/hal+json"
    }
  },
  "_embedded": {
    "customer": {
      "_links": {
        "self": {
          "href": "http://localhost:${serverPort}/api/customers/1",
          "hreflang": "en",
          "type": "application/hal+json"
        }
      },
      "firstName": "Peter",
      "lastName": "River",
      "version": 0
    }
  },
  "id": "0A12321",
  "shippingCost": 13.54,
  "date": "2-08-2017",
  "shippingAddress": {
    "street": "321 Arrow Ln",
    "street2": null,
    "city": "Chicago",
    "state": "IL",
    "zip": 646465
  },
  "products": [
    {
      "_links": {
        "self": {
          "href": "http://localhost:${serverPort}/api/products/6",
          "hreflang": "en",
          "type": "application/hal+json"
        }
      },
      "id": 6
    },
    {
      "_links": {
        "self": {
          "href": "http://localhost:${serverPort}/api/products/11",
          "hreflang": "en",
          "type": "application/hal+json"
        }
      },
      "id": 11
    },
    {
      "_links": {
        "self": {
          "href": "http://localhost:${serverPort}/api/products/1",
          "hreflang": "en",
          "type": "application/hal+json"
        }
      },
      "id": 1
    }
  ]
}

Now clients of our API can access the details of embedded resources, without making additional requests.

//examples are using the fetch API: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API

//retrieve an order instance from the API
fetch("http://localhost:8080/api/orders/1").then(function(response) {
    return response.json();
}).then(function(data) {
    this.order = data;
});

var customer = this.order._embedded.customer;

console.log("Order ID: " + this.order.id);
console.log("Customer: " + customer.firstName + " " + customer.lastName);

//retrieve a product instance from the API
fetch("http://localhost:8080/api/products/1").then(function(response) {
    return response.json();
}).then(function(data) {
    this.product = data;
});

console.log("Product: " + this.product.name);
console.log("Category:" + this.order._embedded.category.name);

5.4 Paginate Results

Another convention specified by the HAL standard is pagination. When serving a list of resources, the _links element can provide first, prev, next and last links, which can be used to navigate the resource list.

The hal helper provides a paginate method that will generate these links and handle the pagination of resources. This method requires a bit more information in the model of our JSON view, in order to keep track of the current offset, max number of records per page, and the total number of resources. In order to do this, we’ll need to create a controller so that we can pass in the needed model parameters.

Let’s use HAL pagination links on our Product resource.

Because we’ll be creating our own ProductController to supply the parameters needed for pagination, we’ll need to remove the @Resource annotation we’ve been using on our Product domain class. Edit grails-app/domain/com/example/Product.groovy:

grails-app/domain/com/example/Product.groovy
package com.example

class Product {

    String name
    String inventoryId

    BigDecimal price

    static belongsTo = [ category : Category ]
}
You’ll often find when developing a RESTful API that the generated controllers and URL mappings from @Resource are a great way to get started, but at some point you’ll want more control over the API - generating the RestfulController yourself is a good solution at that point.

Now, create a new RestfulController using the create-restful-controller command (supplied by the rest-api profile):

$ ./grailsw create-restful-controller com.example.ProductController

Edit this new controller with the following content:

grails-app/controllers/com/example/ProductController.groovy
package com.example

import grails.rest.RestfulController

class ProductController extends RestfulController<Product> {
    static responseFormats = ['json']

    ProductController() {
        super(Product)
    }

    @Override
    def index(Integer max) {
        params.max = Math.min(max ?: 10, 100)

        return [
                productList : listAllResources(params), (1)
                productCount: countResources(),     (2)
                max         : params.max,        (3)
                offset      : params.int("offset") ?: 0, (4)
                sort        : params.sort,  (5)
                order       : params.order  (6)
        ]
    }

    @Override
    boolean getReadOnly() {
        return true
    }
}
1 listAllResource() is a method provided by RestfulController to return a list of all domain resources - you can override this method to control how this list is generated
2 countResources() is another RestfulController method - again, you can override the implementation to suite your API
3 Total number of results per page
4 Offset (used to calculate current page)
5 Property to sort by
6 Direction of sorting

Finally, we need to edit our UrlMappings to create the rest endpoints that were formerly generated using the @Resource annotation. Grails supports a resource property on URL mappings that will generate these URLs automatically. Edit UrlMappings.groovy and add the following rule to the mappings block:

grails-app/controller/com/example/UrlMappings.groovy
"/api/products"(resources: "product")

Now, we can create our new JSON view using pagination. Create the following view and template under grails-app/views/product:

grails-app/views/product/index.gson
import com.example.Product

model {
    Iterable<Product> productList (1)
    Integer productCount (2)
    Integer max
    Integer offset
    String sort
    String order
}

json {
    hal.paginate(Product, productCount, offset, max, sort, order) (3)
    products tmpl.product(productList ?: [])
    count productCount
    max max
    offset offset
    sort sort
    order order
}
1 List of product resources
2 Pagination params from our controller
3 Here we pass the pagination parameters to the paginate method, which will generate the HAL pagination links
grails-app/views/product/_product.gson
import com.example.Product

model {
    Product product
}
json {
    hal.links(product)

    name product.name
    id product.inventoryId
    price product.price
    category product.category.name
}
"""
{
  "_links": {
    "self": {
      "href": "http://localhost:${serverPort}/api/products?offset=0&max=10",
      "hreflang": "en",
      "type": "application/hal+json"
    },
    "first": {
      "href": "http://localhost:${serverPort}/api/products?offset=0&max=10",
      "hreflang": "en"
    },
    "next": {
      "href": "http://localhost:${serverPort}/api/products?offset=10&max=10",
      "hreflang": "en"
    },
    "last": {
      "href": "http://localhost:${serverPort}/api/products?offset=10&max=10",
      "hreflang": "en"
    }
  },
  "products": [
    {
      "_links": {
        "self": {
          "href": "http://localhost:${serverPort}/api/products/1",
          "hreflang": "en",
          "type": "application/hal+json"
        }
      },
      "name": "Cargo Pants",
      "id": "CLOTH001",
      "price": 15.00,
      "category": "Clothing"
    },
    {
      "_links": {
        "self": {
          "href": "http://localhost:${serverPort}/api/products/2",
          "hreflang": "en",
          "type": "application/hal+json"
        }
      },
      "name": "Sweater",
      "id": "CLOTH002",
      "price": 12.00,
      "category": "Clothing"
    },
    {
      "_links": {
        "self": {
          "href": "http://localhost:${serverPort}/api/products/3",
          "hreflang": "en",
          "type": "application/hal+json"
        }
      },
      "name": "Jeans",
      "id": "CLOTH003",
      "price": 15.00,
      "category": "Clothing"
    },
    {
      "_links": {
        "self": {
          "href": "http://localhost:${serverPort}/api/products/4",
          "hreflang": "en",
          "type": "application/hal+json"
        }
      },
      "name": "Blouse",
      "id": "CLOTH004",
      "price": 18.00,
      "category": "Clothing"
    },
    {
      "_links": {
        "self": {
          "href": "http://localhost:${serverPort}/api/products/5",
          "hreflang": "en",
          "type": "application/hal+json"
        }
      },
      "name": "T-Shirt",
      "id": "CLOTH005",
      "price": 10.00,
      "category": "Clothing"
    },
    {
      "_links": {
        "self": {
          "href": "http://localhost:${serverPort}/api/products/6",
          "hreflang": "en",
          "type": "application/hal+json"
        }
      },
      "name": "Jacket",
      "id": "CLOTH006",
      "price": 20.00,
      "category": "Clothing"
    },
    {
      "_links": {
        "self": {
          "href": "http://localhost:${serverPort}/api/products/7",
          "hreflang": "en",
          "type": "application/hal+json"
        }
      },
      "name": "Bookcase",
      "id": "FURN001",
      "price": 40.00,
      "category": "Furniture"
    },
    {
      "_links": {
        "self": {
          "href": "http://localhost:${serverPort}/api/products/8",
          "hreflang": "en",
          "type": "application/hal+json"
        }
      },
      "name": "Coffee Table",
      "id": "FURN002",
      "price": 50.00,
      "category": "Furniture"
    },
    {
      "_links": {
        "self": {
          "href": "http://localhost:${serverPort}/api/products/9",
          "hreflang": "en",
          "type": "application/hal+json"
        }
      },
      "name": "Vanity",
      "id": "FURN003",
      "price": 90.00,
      "category": "Furniture"
    },
    {
      "_links": {
        "self": {
          "href": "http://localhost:${serverPort}/api/products/10",
          "hreflang": "en",
          "type": "application/hal+json"
        }
      },
      "name": "Table Saw",
      "id": "TOOL001",
      "price": 120.00,
      "category": "Tools"
    }
  ],
  "count": 13,
  "max": 10,
  "offset": 0,
  "sort": null,
  "order": null
}

Make a request to the next link, http://localhost:8080/product/index?offset=10&max=10, and you’ll see the next page of results. Due to the small number of resources in our sample data there will only be 2 pages - try changing the max parameter in your request to 4 - you’ll now retrieve additional pages to reflect the smaller page size.

If you’d like, repeat these steps to enable pagination for the other domain resources, such as Order and Customer.

5.5 Custom MIME Types

HAL resources can declare a custom "MIME Type" (or "Content Type") that clients should use in order to interact with the API. Grails includes two generic HAL MIME types in the default application.yml:

grails-app/conf/application.yml
    accept:
        header:
            userAgents:
                - Gecko
                - WebKit
                - Presto
                - Trident
types:
    json:
      - application/json
      - text/json
    hal:
      - application/hal+json
      - application/hal+xml
    xml:
      - text/xml
      - application/xml

You can specify a custom MIME type for your API if you wish, by adding an entry to this configuration:

grails-app/conf/application.yml
        xml:
          - text/xml
          - application/xml
        atom: application/atom+xml
        css: text/css
        csv: text/csv
        js: text/javascript
        rss: application/rss+xml
        text: text/plain
        all: '*/*'
        inventory: "application/vnd.com.example.inventory+json" (1)
urlmapping:
    cache:
        maxsize: 1000
1 Specifies a MIME type called inventory, and gives it a type specification (by convention, vnd indicates a "vendor" MIME type)

Now you can use this custom MIME type in your JSON views, using the type helper method:

grails-app/views/product/show.gson
import com.example.Product

model {
    Product product
}

json {
    hal.links(product)
    hal.embedded(category: product.category)
    hal.type("inventory") (1)
    id product.inventoryId
    name product.name
    price product.price
}
1 The hal.type() method takes a string to identify the custom MIME type in the application.yml file, or an explicit MIME specification as a string

Make a request to http://localhost:8080/api/products/1 to see the custom content-type:

$ curl -i localhost:8080/product/1

HTTP/1.1 200
X-Application-Context: application:development
Content-Type: application/vnd.com.example.inventory+json;charset=UTF-8 (1)
Transfer-Encoding: chunked
Date: Sun, 05 Feb 2017 01:51:08 GMT

        def result = render(view: "/product/show", model:[product: product])

        then:
        result.json._embedded
    }
}
1 Note the new MIME type in the Content-Type header

6 Explore the API

HAL+JSON is a flexible and powerful specification for structuring your API’s output. It allows clients of your API - whether third-party integrations or your own webapps or native apps - to navigate your resources in an efficient and consistent way.

There are many useful tools for developing and testing HAL+JSON APIs. If you use the Google Chrome browser, try installing the JSONView plugin, and navigating your browser to http://localhost:8080/api/products. This plugin will render the JSON in a pleasant format, and allow you to follow links between your resources in your browser.

JSONViewer
Figure 1. JSONViewer plugin for Google Chrome

Another powerful tool for testing and your API is Postman, a Google Chrome-based app available for macOS, Windows, Linux and Chrome OS.

Postman
Figure 2. Postman

7 Do you need help with Grails?

Object Computing, Inc. (OCI) sponsored the creation of this Guide. A variety of consulting and support services are available.

OCI is Home to Grails

Meet the Team