Skip to content

Creating JSON APIs in GRAILS 3.3

July 15, 2017

Creating simple API’s which accept json or get parameters, and respond with json was very easy in grails 2.5.  Unfortunately, many things have subtly changed in grails 3 making porting an app rather difficult, especially as the 3.3 documentation still has 2.5 examples in many places.

Note:

  1. The project you add the API to does not need to be created with the rest-api profile.
  2. The rest-api profile does provide json views, which may or may not be easier (Ill try that another day)
  3. The code below was written from memory, so there will be some typos etc.  Let me know if you find any and update accordingly.

Let us assume we want a simple POST API with loginUser (and other calls)

loginUser will send a JSON request body like this:

{"username":"bob", "password":"pass"}

and expect to get back the following format:

{"result":0, 
 "player":{"username":"bob", "firstname":"bob", "id":1234}, 
 "accounts":[{"balance":1.0, "currencyIso":"GBP", id:1234}]
}

an error would look something like this:

{"result":100, "message":"invalid parameters"}

Here we are using result as a return status/error code, and there are many error codes defined in the API.

Let us assume our domain objects are something like this:

User.groovy

package bla
class User {
  String username
  String password
  String firstname
}

Account.groovy

package bla
Class Account {
  User user
  BigDecimal balance
  String currencyIso
}

Step 1: register the URL

This used to be in Configuration/UrlMappings.groovy. Now it is defined in grails-app/controllers/web.admin/UrlMappings.groovy.

Add your URL thusly:

class UrlMappings {
  static mappings = {
  // stuff
  "/api/v1/user/login"(namespace: "v1", controller: "UserApi", action: "login")
  }
}

Step 2: create your controller (you can do this by simply creating the file or using the create-controller grails command).  Note, here I am putting two classes into one groovy file.  You can split them into two files if you prefer.

UserApiController.groovy

package bla
import grails.converters.JSON

/* The LoginCommand is just to make validating and parsing the JSON input easier
*/
class LoginCommand implements grails.validation.Validateable { 
  String username 
  String password 
  static constraints = { 
     // TODO: share the constraints with the domain object.
     username nullable:false, blank: false, size: 3..32 
     password nullable:false, blank: false, size: 3..32
  }
} // class LoginCommand

class UserApiController {
  static namespace = 'v1'

  def login(LoginCommand cmd) {
  try {
    if (cmd.hasErrors()) { 
        log.error("invalid parameters") 
        render(status: 400, contentType: 'application/json') {
          result 10 
          message "errors in parameters" 
        return
        } 
     } else { 
         // TODO: use a service which will update login failed counters etc
         User user = User.findByUsernameAndPassword(cmd.username, cmd.password)
         if (user == null) { 
            render(status: 404, contentType: 'application/json') { 
                result 100 
                message "could not find user with that username and password" 
            } 
         } else { 
            def userAccounts = Account.findAllByUser(user)
            render(status: 200, contentType: 'application/json') { 
               result 0 
               user {   
                  id user.id   
                  username user.username 
                  firstname user.firstname
               } 
            } 
       } // else all good 
    } // else params ok 
  } catch (Exception e) { 
    log.error("Caught Exception in UserApiController.login()", e) 
    render(status: 500, contentType: 'application/json') { 
        result 9999, 
        message e.toString() 
    } 
  }
}

To test this you can do something like:

http://localhost:8080/api/v1/User/login?username=asdf&password=asdf

Or if you want to use POST, I recommend using https://restlet.com/

The above code works but doesn’t yet return the account info.  Rendering domain objects by hand is easy but hard to maintain – you don’t want to have to duplicate this code in all your apis which return the same objects.  For this there are two options:

  1. object marshallers
  2. json views.

Object marshallers and easy and powerful and work in both grails 2.x and 3.x.  Be aware they are currently not working when you user render(){….} – this is broken. You will need to use JSONBuilder as above and use the respose text parameter.  An object marshaller for our account would be defined in the bootstrap.goovy file thusly:

DecimalFormat df = new DecimalFormat("##0.00"); 
JSON.registerObjectMarshaller(Account) { 
    return [balance: df.format(it.balance), currencyIso: it.currencyIso, id: it.id]
}

Then you can update the login method thusly:

render(status: 200, contentType: 'application/json') { 
  result 0 
  user {   id user.id   username user.username firstname user.firstname } 
  accounts userAccounts
}

 

You can restrict the API to post only using this at the top of the controller:

static allowedMethods = [login: "POST"]

Lastly, don’t forget to run this on SSL only in production to hide the password (not port 80 or 8080), and to encrypt the password in the database (e.g. using a beforeInsert interceptor)

 

Advertisements

From → dev, grails 3

Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: