Current
Previous Versions
Professional
Experience
source: http://tjvantoll.com/2014/12/29/so-you-want-to-write-a-tech-book/Minneapolis, MN
Pumpkin | Halo | Gia | |||
Curly | Juliet | Elway |
Described in Roy Fielding's PhD dissertation in 2000
Fielding, Roy.REpresentational State Transfer (REST) is an architectural style that describes how distributed data objects, or resources, can be defined and addressed, stressing the easy exchange of information and scalability.(Fischer 2013.)
Individual Resources are identified using URIs
Resources are abstracted from the data source and can be manipulated before being sent to a client
Hypermedia As The Engine Of Application State (HATEOAS)
De-coupled server and client means:
Resource Transformation
import grails.rest.Resource
@Resource(uri='/gr8ladies')
class Gr8Lady {
String first
String last
}
URL mappings
URLmappings.groovy
"/gr8ladies"(resources:"gr8lady")
Or
"/gr8ladies"(resources:"gr8lady") {
"/chapters"(controller:"chapter", method:"GET")
}
--> /gr8ladies/{id}/chapters
Controller Responses
class CompanyController {
def index() {
respond company
}
}
uses either the accept header or file extension
Learn the correct status codes for different errors
Code | Description |
---|---|
2XX | Successful |
200 | OK |
4XX | Client Error |
400 | Bad Request |
403 | Forbidden |
404 | Not Found |
5XX | Server Errors |
500 | Internal Server Error |
502 | Bad Gateway |
Based on HTTP Status Dogs and HTTP Status Cats
Many of the cases are handled by Grails or the server(i.e. 5XXs)
if(someCondition) {
render status: 4XX
}
Handled by Default! :)
On each Controller
class Gr8LadyController {
static allowedMethods = [list: "GET", save: "POST", update: "PUT"]
}
URL Mappings
"/gr8ladies"(resource: "gr8lady", method: "GET")
http://example.com/resource
http://example.com/resource/{id}
http://example.com/resource/{id}
http://example.com/resource?id=abc123
http://example.com/resource/{id}/?action=delete
http://example.com/resource/add
/resources
NOT
/ReSoUrCeS
Try Either:
/multiWordResources
Or:
/multi-word-resources
And stick to it. Don't Mix and Match!
plural nouns preferred
http://example.com/users
vs
http://example.com/user
http://example.com/resource?fieldName=value
By Parameter
http://example.com/resource.json
Available formats
grails:
mime:
types:
...
json: ['application/json', 'text/json'],
...
xml: ['text/xml', 'application/xml']
By Resource
import grails.rest.Resource
@Resource(uri='/gr8ladies', formats=['json'])
class Gr8Lady {
...
}
Requesting another type returns a 406 (Not Acceptable)
By RestfulController
class Gr8LadyController extends RestfulController {
static responseFormats = ['json', 'xml']
}
Remember to add any response formats for versioned mime types
Object Marshallers
grails-app/init/Bootstrap.groovy
import grails.converters.JSON
class Bootstrap {
def init = { servletContext ->
JSON.registerObjectMarshaller(Gr8Lady) {
def returnArray = [:]
returnArray['firstName'] = it.first
returnArray['lastName'] = it.last
returnArray['chapter'] = it.chapter?.name
return returnArray
}
}
}
Usage
render gr8lady as JSON
Object Marshallers
grails-app/conf/spring/resources.groovy
import grails.converters.JSON
import org.gr8ladies.ChapterMarshaller
beans = {
JSON.registerObjectMarshaller(new ChapterMarshaller())
}
Object Marshallers
class Gr8LadyMarshaller implements ObjectMarshaller<JSON>{
public boolean supports(Object object) {
return object instanceof Gr8Lady
}
public void marshalObject(Object object, XML converter) {
Gr8Lady gr8lady = (Gr8Lady)object
converter.chars gr8lady.displayName
}
}
link to the resource and navigation
{
"_links": {
"self": { "href": "/gr8ladies" },
"next": [
{ "href": "/gr8ladies?page=2" }
]
}
}
grails-app/conf/spring/resources.groovy
import grails.rest.render.hal.*
beans = {
halGr8LadyRenderer(HalJsonRenderer, org.gr8ladies.Gr8Lady)
}
Usage: Content Negotiation
curl -i -H "Accept: application/hal+json" http://localhost:8080/gr8ladies/1
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/hal+json;charset=ISO-8859-1
{
"_links": {
"self": {
"href": "http://localhost:8080/gr8ladies/1",
"hreflang": "en",
"type": "application/hal+json"
}
},
"name": "Jenn Strater"
}
Parameters
http://example.com/resource?offset=0&max=10
def list() {
List gr8ladies = Gr8Lady.list(max: params.max, offset: params.offset)
respond [results: gr8ladies, max: params.max, offset: params.offset]
}
http://example.com/v1/resource/{id}
GET https://example.com/resource
version: 2.0
GET https://example.com/resource
Accept: application/vnd.example.v2+json
@Resource(uri='/gr8ladies/v1')
@Resource(uri='/gr8ladies/v2')
"/gr8ladies"(version:'1.0', resources:"gr8lady", namespace: 'v1')
"/gr8ladies"(version:'2.0', resources:"gr8lady", namespace: 'v2')
@Resource(uri="/gr8ladies", readOnly=true)
class Gr8Lady {
String first
...
}
Token based Authentication based on Spring-Security
Last Updated May 2015
Greach 2014
VideoFor more complicated APIs
Last Updated November 2014
For greater control over data output
Last Updated February 2014
jenn@gr8ladies.org