JSON Hypermedia Api with forms and links
Asked Answered
M

5

20

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!

Mahau answered 24/11, 2012 at 14:52 Comment(1)
Pretty late answer but Mike (author of the Hal spec) has the Halo spec in the works that addresses forms and supplements Hal. groups.google.com/forum/?fromgroups=#!msg/api-craft/7ywrN0u-6_E/…. Plus examples here: gist.github.com/mikekelly/893552 . Not sure how active the developement on that spec is though.Fortunate
S
7

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
                }
            }
        }
    }
    
Sjambok answered 13/9, 2014 at 0:50 Comment(0)
I
5

The JSON Schema standard (particularly "hyper-schemas") definitely allows this. You reference a JSON (Hyper-)Schema (using HTTP headers) and the schema defines rules on how to interpret your data as hyper-text.

The information for constructing your links can be anywhere. The hyper-schema documents how to assemble link URIs from the data (it can be a template), and they also specify HTTP method, encoding type, and so on.

To get form functionality: you can specify a full schema for the data to be submitted along with the request. Required/optional properties, array length constraints, whatever.

As a demo, here's part of a walkthrough for a JavaScript library that understands hyper-schemas and can present an appropriate form for links: jsonary.com.

Inapprehensible answered 18/1, 2013 at 18:17 Comment(0)
E
5

I have been working on an API, using JSON Hyper Schema. You can browse aroun, and even register, login and do some actions. Check it out, here: http://api.psprt.com

[EDIT] See my latest stuff here: www.passportedu.com https://github.com/bpanahij/HypermediaServer https://github.com/bpanahij/client-schema.json

I also open sourced the API code: https://github.com/bpanahij/passportedu_schema

Feel free to take a look, borrow, and comment.

JSON Hyper Schema (See Also JSON-Schema) has a way to specify forms, through the properties member:

{
"id": "/api/v1",
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "PassportEDU API",
"name": "PassportEDU API",
"type": "object",
"description": "Bringing global students together with global schools.",
"links": [
   {
      "title": "Log In",
      "rel": "authenticate",
      "href": "/api/v1/authenticate",
      "method": "POST",
      "properties": {
        "username": {
          "title": "Your username",
          "description": "Your email address or username",
          "type": "string"
        },
        "password": {
          "title": "Your password",
          "description": "Your password",
          "type": "password"
        }
      },
      "required": ["username", "password"]
   }
   ]
}
Edith answered 24/12, 2013 at 21:45 Comment(3)
Your example is controversial. It is okay to add forms to hal (or whatever MIME type you use as base), but the example is about login, which you will never find in a REST webservice, since the requests must be stateless by REST. ics.uci.edu/~fielding/pubs/dissertation/…Sjambok
In fact the request is only meaningful if it carries state. A server should be stateless. Logging in is a stateless operation, in that it always has the result of the user being logged in after success. HATEOS: the ultimate state of REST, stands for: Hypermedia as the Engine of Application State. The promise it delivers is decoupling state from your application code, and instead allowing the user to pass state into the application. This is why HATEOAS is so powerful: it can scale very easily, and reduces the need for the server to keep state.Edith
@Sjambok That is only one of a number of things that make good APIs: What you are referring to is what Leonard Richardson refers to as Level 1 of REST: Resources (martinfowler.com/articles/richardsonMaturityModel.html) REST APIs can go well beyond simply representing a data model with CRUD. They can represent the state of an application. A design in which the application follows CRUD and SOLID principles, will comply with REST. Adding hypermedia controls, like content negotiation and links, only makes for a better application.Edith
A
4

Check out Collection+JSON, HAL, and/or Siren.

Astraddle answered 24/11, 2012 at 22:22 Comment(3)
First Look at HAL: B.5. Why does HAL have no forms? Omitting forms from HAL was an intentional design decision that was made to keep it focused on linking for APIs. HAL is therefore a good candidate for use as a base media type on which to build more complex capabilities. An additional media type is planned for the future which will add form-like controls on top of HAL. So that isn't really a candidate... will look at the others.Mahau
I don't see any of these describing forms, so doesn't really answer my question.Mahau
Siren has forms, they just call it "actions". It supports most if not all of the control types defined in HTML 5.Phylactery
S
0

There currently isn't a publicly specced, general purpose JSON format with forms, as far as I am aware. You're free to define one if you need it and publish the specifcations. As a personal preference, I recommend basing it upon HAL.

If you do decide to write one of your own, please create a mailing list and invite others to participate. If you don't, you'd be in danger of tailoring it too closely to meet just your own needs and accidentally overlooking some requirement that prevents it from being widely applicable.

Sungsungari answered 3/12, 2012 at 8:22 Comment(2)
Hi Nicholas, I'll consider doing that. I was hoping someone had done this alerady, since the task seems to grow as I attack it. It is no simple task, especially if you would like to specify objects in your post body instead of just fields. As an example I would like a certain endpoint to accept a post with a customer object, and not really have to specify every field in that object as post fields. What do people do when they want to do that in an XHTML Rest Hypermedia API?Mahau
Either PATCH to the URI, or define that a POST request to a URI does an update of any supplied fields, and a PUT request to the API causes any unpassed fields to get set to their default values. I consider allowing POST to a non-collection resource bad practice for M2M APIs, and only barely acceptable for browser-based APIs due to limitations in current web browsers.Sungsungari

© 2022 - 2024 — McMap. All rights reserved.