Summary: Web Api Design the Missing Link

The ebook: .PDF

Relationship between models

Assume that there is a relationship between a dog and its owner. A popular way to represent it:

{
  "id": "12345678",
  "name": "Lassie",
  "ownerID": "98765432" // the relationship
}

A better way:

{
  "id": "12345678",
  "name": "Lassie",
  "ownerID": "98765432",
  "ownerLink": "https://dogtracker.com/persons/1337" // Use links
}

The owner representation:

{
  "id": "98765432",
  "kind": "Person",
  "name": "Joe Carraclough",
  "dogsLink": "https://dogtracker.com/persons/98765432/dogs" // 🐶
}

Suppose dogs can be owned by either people or institutions (e.g. companies). At this point, it is no longer enough to have an ownerID property to reference the owner—you also need to specify what type of owner it is.

Multiple solutions are possible:

But a very elegant and flexible option is to use links to solve the problem:

{
  "self": "https://dogtracker.com/dogs/12345678",
  "id": "12345678",
  "kind": "Dog",
  "name": "Lassie",
  "owner": "https://dogtracker.com/persons/98765432"
}

The server should strip the scheme and authority from URLs that identify its own resources before storing them, and put them back when they are requested. Any URLs that identify resources that are completely external to your API will probably have to be stored as absolute URLs. If you are not very experienced with absolute URLs in databases, don’t do it.

Collections

{
  "self": "https://dogtracker.com/dogs",
  "kind": "Collection",
  "contents": [
    {
      "self": "https://dogtracker.com/dogs/12344",
      "kind": "Dog",
      "name": "Fido"
    },
    {
      "self": "https://dogtracker.com/dogs/12345",
      "kind": "Dog",
      "name": "Rover"
    }
  ]
}

The contents field as an array of URLs would be another good option if your clients don’t need any data about each of the content resources.

Collection+JSON

Paginated collections

>>>

GET /dogs HTTP/1.1
Host: dogtracker.com
Accept: application/json

<<<

HTTP/1.1 303 See Other
Location: https://dogtracker.com/dogs?limit=25,offset=0

Response:

{
  "self": "https://dogtracker.com/dogs?limit=25,offset=0",
  "kind": "Page",
  "pageOf": "https://dogtracker.com/dogs",
  "next": "https://dogtracker.com/dogs?limit=25,offset=25",
  // previous: URL
  // first: URL
  // last: URL
  "contents": [
    {
      "self": "https://dogtracker.com/dogs/12344",
      "kind": "Dog",
      "name": "Fido",
    },
    {
      "self": "https://dogtracker.com/dogs/12345",
      "kind": "Dog",
      "name": "Rover",
    }
    // … (23 more)
  ]
}

Designing URLs

In URLs, nouns are good; verbs are bad.

Path parameters, or matrix parameters

// Pattern

_/{relationship-name}[;{selector}]/…/{relationship-name}[;{selector}]_

// Examples

/persons;5678/dogs
/persons;id=5678/dogs # synonym for the previous URL
/persons;name=JoeMCarraclough/dogs

Responses that don’t involve persistent resources

API calls that return a response that is not the representation of a persistent resource are common. In these cases, it is not necessary to depart from our noun-based, document-based model. The key insight is that the URL should identify the resource in the response, not the processing algorithm that calculates the response.

Examples:

/monetary-amount?quantity=100&unit=EUR&in=CNY
/monetary-amount/100/EUR/CNY

GET content negotiation

Req:

GET /monetary-amount/100/EUR HTTP/1.1
Host: currency-converter.com
Accept-Currency:CNY

Res:

683.92CNY

POST noun-based

Req:

POST /currency-converter HTTP/1.1

{
"amount": 100,
"inputCurrency": "EUR",
"outputCurrency": "CNY"
}

Res:

683.92

Partial response

/joe.smith/friends?fields=id,name,picture

Handling Errors ??

Modeling Actions ??

Auth: OAuth2 always