Rest API

Sun 24 May 2015

Some notes from a video I watched on youtube.

Fundamentals

Resources

  • Nouns, not verbs
  • Coarse grained, not fine grained

I.e

/getAccount /createDirectory /updateGroup /verifyAccountEmailAddress

does not scale because resources is tightly coupled to behaviours.

Resource types

Collection resources

/applications

  • self documenting, plural of the instance resources

Instance resources

/applications/a1b2c3

Behavior

  • GET
  • PUT
  • POST
  • DELETE
  • HEAD (metadata request)

Post, Get, Put, Delete does not neccessery have a one-to-one mapping to CRUD.

GET = READ DELETE = DELETE HEAD = Headers, no data

PUT and POST can both be used for CREATE and UPDATE

Using PUT to CREATE or UPDATE: /applications/myapplicationid

PUT can be used to create a resource where the resourceid is given by the PUT.

PUT request can not contain partial data, it must contain all data that is needed to CREATE or UPDATE the resource. This is because PUT is idempotent according to the HTTP specification. This mean that a PUT request gives the same result if it is executed one or more times. In our REST API that means that the serverstate is the same after the first and fifth PUT request.

Using POST to CREATE: /applications

POST is used to create a resource on the parent resource, i.e the resource id is not given in the POST request. When using POST to CREATE its important to give a 201 response to tell the client where the new resource is living, i.e "Location: https://fqdn/applications/myapplicationid".

Using POST top UPDATE: /apllications/myapplicationid

PUT can be used to do a partial update of a resource. POST is not idempotent according to the specifications. You can make it idempotent, but you dont have to. It is important to give a 200 ok response if the update succeed.

Media types

  • Format specifications + parsing rules
  • Requests: Accept header
  • Response: Content-Type header

The rest client set a comma separated list of mediatypes in the Accept header, i.e "application/json, plain/text", meaning that the client support both but prefer "application/json".

The server set the Content-Type header telling the client what it actually can have.

  • application/json , global accepted json mediatype
  • application/foo+json , your own mediatype (can be registered with IANA to be global)
  • application/foo+json;application , your own mediatype and resourcetype

Design

Base URL

  • Keep the baseurl short

http(s).api.foo.com VS http(s)://www.foo.com/dev/service/api/rest

  • Clients can be REST applications or webbrowser. Threat the in the same way. If you do, developers can test and debug their application by using the webbrowser to investigate your API.

Versioning

URL: https://api.foo.com/v1

  • Clean and concise
  • Can be cumbersome if versionnumbers changes often so try to keep it stable
  • Pragmatic

or

Mediatype: application/json+foo;application&v1

  • more flexible for server developers
  • more heeavyweight and complicated with custom mediatypes
  • idealistic

Resource format

Coventions

camelCase

JS in JSON = JavaScript

myCollection.forEach

not

myColection.for_each

Underscores are unconventional in JavaScript, stay consistent.

Date / Time / Timestamp

There is a standard, ISO 8601, use it!

2015-05-24 06:07Z
2015-W21-7 06:07Z

Also use UTC to avoid problems.

HREF
  • Distributed hypermedia is paramount!
  • Every accessible resource should have its own unique URL
  • Replace IDs (IDs exist, but are opaque)
  • Always return a resource unique URL as a property when its consumed
{
href: https://www.foo.com/dev/service/api/rest/apllications/myapplicationid
}
Response body

Should you always return a response body when queried?

  • If its a GET, its obvious
  • If feasible, return the representation in the response
  • Allow override, ?_body=false, for control
Content negotiation
  • Header, Accept

Comma separated list of values, in order of preference

GET /apllications/myapplicationid
Accept: application/json, text/plain
  • Resource Extension

Conventionally overrides the Accept header

GET /apllications/myapplicationid.json
GET /apllications/myapplicationid.csv

Resources References, linking

  • Linking is fundamental to scalability
  • Tricky in JSON
  • XML has it (XLink), JSON doesnt
Instance reference
GET /apllications/myapplicationid
200 OK
{
"href": "https://www.foo.com/dev/service/api/rest/apllications/myapplicationid",
"property1": "Value1",
"property2": "Value2",
... ,
"directory": {
      "href": ""https://www.foo.com/dev/service/api/rest/directories/someid"
    }
}

##### Collection reference

GET /apllications/myapplicationid
200 OK
{
"href": "https://www.foo.com/dev/service/api/rest/aplications/myapplicationid",
"property1": "Value1",
"property2": "Value2",
... ,
"groups": {
      "href": ""https://www.foo.com/dev/service/api/rest/aplications/myapplicationid/groups"
    }
}

#### Reference Expansion

How to obtain a resource and its linked properties in the same repsonse?

GET /applications/myapplicationid?epxand=directory
200 OK
{
"href": "https://www.foo.com/dev/service/api/rest/apllications/myapplicationid",
"property1": "Value1",
"property2": "Value2",
... ,
"directory": {
      "href": "https://www.foo.com/dev/service/api/rest/directories/someid",
      "name": "Name of a directory",
    }
}
#### Partial representations
GET /applications/myapplicationid?fields=property2, directory(name)
200 OK
{
"property2": "Value2",
... ,
"directory": {
      "name": "Name of a directory",
    }
}

#### Pagination

Collection resource should support query parameters:

* Offset
* Limit
../applications?offset=50&limit=20
or you could embed the needed information in the collection resource itself.
GET /accounts/id/groups
200 OK
{
"href": "../accounts/id/groups",
"offset": 0,
"limit": 25,
"first": { "href": "../accounts/id/groups?offset=0" },
"previous": null,
"next": { "href": "../accounts/id/groups?offset=25" },
"last": { "href": ".." },
"items": [
    {
        "href": ".."
    },
    {
        "href": ".."
    },
    ...
]
#### Many to many Group to account: * Group can have many accounts * An account can be in many groups * Each mapping is a resource Use GroupMembership as a resource that represents all the groups an account is assosiated with.
GET /groupMemberships/someuniqueid

200 OK
{
    "href": "../groupMemberships/someuniqueid",
    "account": {
        "href": "..",
    },
    "group": {
        "href": ".."
    },
    ...
}

Which will give us a accountobject that looks like:

GET /applications/myapplicationid
200 OK
{
"href": "../aplications/myapplicationId",
"property1": "Value1",
"property2": "Value2",
... ,
"groups": {
      "href": "../applications/myapplicationId/groups"
    },
"groupMemberships": {
      "href": "../applications/myapplicationid/groupMemberships?applicationId=myapplicationId"
    }
}
#### Errors * As descriptive as possible * As much information as possible * Developers are your customers Example:
POST /directories
409 Conflict
{
    "status": 409,
    "code": 40924,
    "property": "name",
    "message": "A resource named 'Somename' already exists.",
    "developerMessage": "A resource named 'Somename' already exists. If you have
    a stale local cache, please exire it now.",
    "moreInfo": "https://fqdn/docs/api/errors/40924"
}
## Security ### General tips * Avoid sessions when possible ** Authenticate every request if neccessary ** Stateless * Authorize basd on resource content, NOT URL * Use existing protocol: ** Oauth 1.0a, Oauth2, Basic over SSL only * Custom authentication scheme: ** Only if you provide client code / SDK ** Only if you really, really know what you are doing * Use API keys instead if username/passwords ### 401 vs 403 * 401 "Unauthorized" really means Unauthenticated ** You need valid credentials for me to respond to this request * 403 "Forbidden" really means Unauthorized ** I understood your credentials, but you are not allowed ### HTTP Authentication schemes * Server response to issue challenge:
WWW-Authenticate: 
realm="Application name"
* Client request to submit credentials:
Authorization: 
### API keys * Entropy * Password reset * Independence * Speed * Limited exposure * Traceability ### Ids * IDs should be opaque * Should be globally unique * Avoid sequential numbers (contention, fuskin) * Good candidates: UUIDs, Url64 ### HTTP method overrides If a client does not support other methods than i.e POST and GET you can add method overrides to support the remaning methods.
POST /applications/myapplicationId?_method=DELETE
Always to method overrides in POST requests due to the idempotency. ### Caching & Concurrency control * Server (initial response): ** ETAG: "6483483485jdfjg39" * Client (later request): ** If-None-Match: "6483483485jdfjg39" * Server (later response): ** 304 Not Modified Remember that if you use SSL, caching is not an issue ### Maintaince * Use HTTP redirects * Create abstraction layer / endpoints when migrating * Use well defined custom media types
Tagged as : rest api