JSON Hypermedia Api with forms and links

Jay Pete picture Jay Pete · Nov 24, 2012 · Viewed 8.4k times · Source

I am in the early stages of planning a REST api, and I would like for it to adhere to the HATEOAS constraint of REST. But I would also like to provide a JSON format. So my question is if there are conventions out there to represent links and forms in JSON.

I have found examples of links, and it looks like this is a pretty common way of representing links:

"links": [ 
{"rel": "self", "href":"http://example.org/entity/1"},
{"rel": "friends", "href":"http://example.org/entity/1/friends"}] 

Representing forms on the other hand, is not something that I have seen much of. I was thinking that perhaps somebody had sat down and thought up something along these lines, but considered all the caveats:

"forms" : [
{"rel" : "new client", "action" : "/clients", "method": "post", 
"fields" : ["name":"string", "zipcode":"int", "signedup":"date", "state": ["Alabama",...]...]}]

The inspiration for this comes from looking at this video, where Jon Moore suggests that JSON is not a good format for a hypermedia api:

http://oredev.org/2010/sessions/hypermedia-apis

A really good talk by the way!

All input is appreciated!

Answer

inf3rno picture inf3rno · Sep 13, 2014

I have investigated this topic for a while, but I am not certain about which possible solutions ppl use and which not. There are only a few examples available... So I'll need some review from experts... (My examples will be mostly in HAL+JSON.)

1.)

I have a feeling that link relations should be GET only, because in HTML they are for including things like stylesheets. I guess other ppl had the same feeling, because there is an edit-form and a create-form by IANA link relations.

  • So the first possible solution to dereference links with form relations and so download the form descriptions for the write operations. These form descriptions can contains HTML fragments or a schema which we can use to generate the forms. For example

    Just to mention you can use the same approach by sending the same links in the header and send raw JSON as body.

    {
        "_links": {
            "edit-form": {
                "href": "http://example.com/users/1?form=edit",
                "type": "text/html",
                "title": "Edit user"
            }
        }
    }
    

So what if link relations are not just for read purposes?

2.)

Then we can use the built-in features of HAL:

  • If we send data, then we can use the type to describe the request body instead of the response body. Ofc. in this case there should not be a response body, or this solution will be confusing.

        {
            "_links": {
                "curies": [
                    {
                        "name": "my",
                        "href": "http://example.com/rels/{rel}",
                        "templated": true
                    }
                ],
                "my:edit": {
                    "href": "http://example.com/users/1",
                    "type": "application/vnd.example.user+json",
                    "title": "Edit user"
                }
            }
        }
    

    So in this case the client will know that my:edit means that this is an edit form, and by checking the MIME type it will know what type of form to display.

  • An alternative solution to use the custom link relation for the same purpose:

        {
            "_links": {
                "curies": [
                    {
                        "name": "my",
                        "href": "http://example.com/rels/{rel}",
                        "templated": true
                    }
                ],
                "my:edit-user": {
                    "href": "http://example.com/users/1",
                    "type": "application/json",
                    "title": "Edit user"
                }
            }
        }
    

    So by fetching the docs http://example.com/rels/edit-user we can find a description about how to build a form for editing users and so we can support the my:edit-user link relation in our client. The docs can contain optionally a HTML form or some schema, or an RDF document using a form description vocab, etc...

  • We can follow the same approach by the profile property of the links. For example:

        {
            "_links": {
                "curies": [
                    {
                        "name": "my",
                        "href": "http://example.com/rels/{rel}",
                        "templated": true
                    }
                ],
                "my:edit": {
                    "href": "http://example.com/users/1",
                    "type": "application/json",
                    "title": "Edit user",
                    "profile": "http://example.com/profiles/user"
                }
            }
        }
    

    So in here the link relation means that this is an edit form and the profile describes how to generate the form under the http://example.com/profiles/user URL.

3.)

Or we can extend HAL using custom properties.

  • For example dougrain-forms does this:

        {
            "_forms": {
                "edit": {
                    "href": "http://example.com/users/1",
                    "headers": {
                        "content-type": "application/json"
                    },
                    "title": "Edit user",
                    "method": "PUT",
                    "schema": {
                        "required": [
                            "name"
                        ],
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string"
                            }
                        },
                        "title": "user properties"
                    }
                }
            }
        }
    
  • But you can use any alternative approach as long as we don't have a standard about HAL and about HAL forms, for example I would rather use a mongoose schema like solution:

        {
            "name": "John",
            "_links": {
                "curies": [
                    {
                        "name": "my",
                        "href": "http://example.com/rels/{rel}",
                        "templated": true
                    }
                ],
                "my:edit": {
                    "href": "http://example.com/users/1",
                    "type": "application/json",
                    "title": "Edit user",
                    "method": "PUT",
                    "_embedded": {
                        "schema": {
                            "name": "String"
                        }
                    }
                }
            }
        }
    

4.)

Don't use link relations and simple JSON formats like HAL, use RDF with one or more vocabulary instead. It is harder to use RDF, but it is a fine grained solution for decoupling clients from REST services, while HAL is just a coarse grained solution...

  • For example JSON-LD with Hydra and a custom vocab:

    {
        "@context": [
            "http://www.w3.org/ns/hydra/core",
            "https://example.com/docs#"
        ],
        "@id": "https://example.com/users/1",
        "name": "John",
        "operation": {
            "@type": "ReplaceResourceOperation",
            "title": "Edit user",
            "method": "PUT",
            "expects": {
                "@id": "https://example.com/docs#User",
                "supportedProperty": {
                    "@type": "SupportedProperty",
                    "title": "name",
                    "property": "https://example.com/docs#User.name",
                    "range": "http://www.w3.org/2001/XMLSchema#string",
                    "required": true
                }
            }
        }
    }