JSON RESTful API for GORM

  • Tags: ajax, rest, restful, json
  • Latest: 1.0.11
  • Last Updated: 31 October 2011
  • Grails version: 1.3.0 > *
  • Authors: Matthias Hryniszak
8 votes
Dependency:
compile "org.grails.plugins.json-rest-api:json-rest-api:1.0.11"

 Documentation  Source  Issues

Summary

Installation

grails install-plugin json-rest-api

Description

Please note that all development (including the examples) have recently been migrated to GitHub. Please update your branches!

See GitHub for known issues.

Sources: https://github.com/padcom/grails-json-rest-api

Continuous integration: http://dev.aplaline.com/hudson/job/grails-json-rest-api/

Installation

grails install-plugin json-rest-api

Usage

After installing this plugin a subset of RESTful API is available under /<i>context/api/domain-class-name . This means:

  • GET on /context/api/domain-class-name returns a list of domain objects (possible arguments are the same as for the DomainClass.list() method argument map)
  • POST on /context/api/domain-class-name creates a new instance
  • GET on /context/api/domain-class-name/id retrieves the given instance
  • PUT on /context/api/domain-class-name/id updates the given instance by ID
  • DELETE on /context/api/domain-class-name/id deletes the given instance
The "context" part of the paths above is the application context (note the leading forward slash). In your case it's going to be different! For example if your application is called "my-app" it's going to be /my-app/api/domain-class-name/

Mappings

Domain class names are mapped using a static expose property, for example:

package domain

class Person { static expose = 'person'

String firstName String lastName }

becomes /context/api/person

package domain

class PersonAddress { static expose = 'person-address'

Long personid String address }

becomes /context/api/person-address

Any parameter that works with the DomainClass.list(params) method works out of the box so paging and probably server-side sorting work.

Domain classes and relations

Since version 1.0.6 this plugin supports list and single instances of relations. There is however one known limitations to that.

  • domain classes have to have numeric id
For example if an Author hasMany = [ books: Book ] then the following will be the outcome of GET on /api/author/1:

{ data: { books: [1, 2, 3], name: "Author name" }, success: true, message: "", count: 1 }

Quering /api/book/1 will yield the following result:

{ data: { author: 1, title: "Some title" }, success: true, message: "", count: 1 }

If you want you can change the book-to-author assignment by sending PUT with the following payload at /api/book/1:

{ data: { author: 2 } }

If the Author with id=2 does not exists it will not be retrieved from the database thus a null value will be assigned to the field. If the author cannot be null then an error will be returned just like you'd assign a null directly to the field.

This has been a long awaited feature. If you find it useful or if it breaks please drop me a note (padcom@gmail.com)

Changing the root for the API

Changing the root for the API is possible using the following configuration option in Config.groovy:

grails.'json-rest-api'.root = '/json'

With this line in place instead of /context/api the services will be available under /context/json.

Custom retrieval functions and field exclusion

To customize how instances are retrieved (instead of using the DomainClass.list() method) there's a special static property called api that by convention should contain 2 closures:

  • excludedFields = list to mark certain fields as being excluded from the rendered response
  • list(params) to retrieve the actual instances
  • count(params) to retrieve total count of instances
The params parameter is the exact same one passed on to controller. Here's an example that shows a customized api:

package domain

class Person { static expose = 'person'

static api = [ excludedFields: [ "fullName" ] list : { params -> Person.list(params) }, count: { params -> Person.count() } ]

String firstName String lastName

String getFullName() { "${firstName} ${lastName}" } }

This is a pretty useless example but it shows the way to implement custom retrieval functions.

Please bear in mind that both functions need to operate on the same set of data so if the full list retrieved by the list closure contains 100 elements this is the exact number to be retrieved by the count closure. It's best achieved by using the same criteria for both closures.

Examples

https://github.com/padcom/grails-json-rest-api-examples

To use those examples you need to have them cloned on the same level in the same folder as the actual plugin so that the directory structure looks like this:

grails-json-rest-api
grails-json-rest-api-examples

Motivation

The main motivation behind this plugin was to make it extremely easy to use GORM and Grails with Ext JS and the JsonStore it provides. So for example, to utilize the JsonStore for a Person domain class you'd normaly define a read-only store like this:

var personStore = new Ext.data.JsonStore({
  url: '/example/api/person',
  restful: true,
  root: 'data',
  totalProperty: 'count',
  messageProperty: 'message',
  fields: [ 
    { name: 'id', type: 'int' }, 
    { name: 'firstName' },
    { name: 'lastName' } 
  ]
});

To provide writing capabilities the following store definition is sufficient for all CRUD operations:

var store = new Ext.data.JsonStore({
  url: '/example/api/person',
  restful: true,
  root: 'data',
  totalProperty: 'count',
  messageProperty: 'message',
  fields: [
    { name: 'id', type: 'int' },
    { name: 'firstName', allowBlank: false },
    { name: 'lastName', allowBlank: false }
  ],
  writer: new Ext.data.JsonWriter({
    encode: false
  })
});

To provide remote pagination and sorting capabilities define your store with an additional paramNames property like this:

var store = new Ext.data.JsonStore({
  url: '/example/api/person',
  restful: true,
  root: 'data',
  totalProperty: 'count',
  messageProperty: 'message',
  paramNames: { start: 'offset', limit: 'max', sort: 'sort', dir: 'order' },
  fields: [
    { name: 'id', type: 'int' },
    { name: 'firstName', allowBlank: false },
    { name: 'lastName', allowBlank: false }
  ],
  writer: new Ext.data.JsonWriter({
    encode: false
  })
});

This ensures that the parameters required by DomainClass.list() method match what JsonStore is sending.

Changes

1.0.8

  • added option to exclude properties from rendered JSON

1.0.7

  • fixed problem with response being 200 instead of 500 when it was not possible to create an instance

1.0.6

  • added one-level traversing of relations when rendering JSON