REST APIs

Best Practices

Why?

Outline

  • APIs, REST & JSON
  • REST Fundamentals
  • Design
    • Base URL
    • Versioning
    • Resource format
    • Return Values
    • Content Negotiation
    • References (linking)
    • Pagination
    • Query Parameters
    • Associations
    • Errors 
    • IDs
    • Method Overloading
    • Resource Expansion
    • Partial Responses
    • Caching & Etags
    • Security
    • Multi Tenancy
    • Maintenance

APIs, REST & JSON

APIs

  • Applications
  • Developers
  • Pragmatism over Ideology
  • Adoption (make developers happy!!)
  • Scale

Why REST?

  • Scalability (dissemination)
  • Generality (HTTP)
  • Independence
  • Latency (Caching)
  • Security
  • Encapsulation

Why JSON?

  • Ubiquity
  • Simplicity
  • Readability
  • Scalability
  • Flexibility

HATEOAS

  • Hypermedia
  • As
  • The
  • Engine
  • Of
  • Application
  • State

Further restriction on REST architectures.

REST Is Easy

REST is *&@#$! Hard

(for Providers)

REST can be Easy

(if you follow some guidelines)

Problem Domain: Churchill


Problem Domain: Churchill.com

  • Navigation L1, L2
  • Vehicles
    • Gallery
    • Specifications
    • Offers
  • Owner Accounts
    • Email verification
    • Email validation
    • Account management
    • Vehicle management

Fundamentals

Resources

  • Nouns, not Verbs.
  • Coarse Grained, not Fine Grained.
  • Architectural style for use-case scalability.

What if?

/getNavigation

/createAccount

/updateVehicle

/verifyAccountEmailAddress

What if?

/getNavigation
/getAllNavigationItems
/searchAccounts
/createVehicle
/createAccount
/updateVehicleName
/updateUserInformation
/findVehiclesByUser
/searchContentByTitle
/verifyAccountEmailAddress
/verifyAccountEmailAddressByToken
...
Smells like bad RPC. DON'T DO THIS.

Keep It Simple

The Answer

Fundamentally two types of resources:

  • Collection Resource
  • Instance Resource

Collection Resource

/accounts

Instance Resource

/accounts/a1b2c3

Behavior

  • GET
  • PUT
  • POST
  • DELETE
  • HEAD (Metadata)
  • PATCH (HTTP1.1 -  IE8?? )

Behavior

POST, GET, PUT, DELETE

!= 1 : 1

Create, Read, Update, Delete

Behavior 

As you would expect:

GET = Read
DELETE = Delete
HEAD = Headers, no Body

Not so obvious:

PUT and POST can both be used for Create and Update.

PATCH for Update.

PUT for Create

Identifier is known by the client:

PUT /accounts/username

{
...
}

PUT for Update

Full Replacement

PUT /accounts/username
{
"name": "Juan Perez",
"vehicles": [{
...
}]
...
}

PUT




Idempotent


(Send all the fields in the request)

PATCH

Partial updates

Non idempotent. 

PATCH /accounts/username
{
"name": "Juan Perez"
}

POST as Create

On a parent resource

POST /accounts
{
"name": "Juan Perez"
}

201 Created
Location: https://api.kia.com/owners/data/accounts/a1b2c3

POST as Update

On instance resource

POST /accounts/a1b2c3

{
"name": "Juan Perez"
}

Response:
200 OK

POST




NOT Idempotent

Media Types


  • Format Specification + Parsing Rules
  • Request: Accept Header
  • Response: Content-Type header

  • application/json
  • application/kia+json
  • application/kia+json;account
  • ...

REST Design

Design Time!

Base URL

http(s)://api.kia.com


Vs

http://www.kia.com/dev/service/api/rest

http(s)://api.kia.com


Rest Client

Vs

Browser

Versioning

URL 

https://api.kia.com/v1

VS

Media-Type

application/json+kia;account&v=1

DON'T USE 1.3.4

Resource Format

Media Type 


Content-Type: application/json

When time allows:

application/kia+json
application/kia+json;account&v=1
...

CamelCase

'JS' in 'JSON' = JavaScript

myArray.forEach
Not myArray.for_each

account.givenName
Not account.given_name

Underscores for property/function names are unconventional for JS. Stay consistent.

Date/Time/Timestamp

There's already a standard. Use it: ISO 8601

Example:
{
...,
"createdTimestamp": "2014-04-10T18:02:24.354Z"
}

Use UTC!

HREF

  • Distributed Hypermedia is paramount!
  • Every accessible Resource has a unique URL.
  • Replaces IDs (IDs exist, but are opaque).

{
"href": "https://api.kia.com/v1/accounts/x7y8z9",
...
}

Critical for linking, as we'll soon see

Response Body

GET obvious


What about POST?


Return the representation in the response when feasible.

Add override (?_body=false) for control

Content Negotiation

Header


  • Accept header
  • Header values comma delimited in order to preference

GET /accounts/a1b2c3
Accept: application/json, text/plain

Resource Extension


/accounts/a1b2c3.json
/accounts/a1b2c3.csv
...

Conventionally overrides Accept header

Resource References

aka 'Linking'


  • Hypermedia is paramount.
  • Linking is fundamental to scalability.

  • Tricky in JSON
  • XML has it (XLink), JSON doesn't
  • How do we do it?


Instance Reference


GET /accounts/x7y8z9

200 OK
{
"href": "https://api.kia.com/v1/accounts/x7y8z9",
"givenName" : "Tony",
"surname": "Stark",
...,
"vehicle": ????
}

Instance Reference


GET /accounts/x7y8z9

200 OK
{
"href": "https://api.kia.com/v1/accounts/x7y8z9",
"givenName" : "Tony",
"surname": "Stark",
...,
"vehicle": {
"href": "https://api.kia.com/v1/vehicles/g4h5i6"
}
}

Collection Reference


GET /navigations/x7y8z9

200 OK
{
"href": "https://api.kia.com/v1/navigations/x7y8z9",
"givenName" : "Tony",
"surname": "Stark",
...,
"items": {
"href": "https://api.kia.com/v1/navigations/x7y8z9/items"
}
}

Reference Expansion

(aka Entity Expansion, Link Expansion)

Account and its Vehicle?


Expand Parameter

GET /accounts/x7y8z9?expand=vehicle


200 OK
{
"href": "https://api.kia.com/v1/accounts/x7y8z9",
"givenName" : "Tony",
"surname": "Stark",
...,
"vehicle": {
"href": "https://api.kia.com/v1/vehicles/g4h5i6",
"name": "Avengers Sportage",
"creationDate": "2014-04-15T15:55:52.5654",
...
}
}

Partial Representations

GET /accounts/x7y8z9?fields=firstName,surName,vehicle(name)

Pagination

Collections Resource supports query params:

  • Offset
  • Limit

/accounts/x7y8z9/vehicles?offset=50&limit=25

RESPONSE

GET /accounts/x7y8z9/vehicles

200 OK
{
"href": ".../accounts/x7y8z9/vehicles",
"offset": 0,
"limit": 25,
"first": { "href": ".../accounts/x7y8z9/vehicles?offset=0"},
"previous": null}
"next": 
{ "href": ".../accounts/x7y8z9/vehicles?offset=25"},
"last": { "href": "..."},
"items": [
{ href: "..."},
{ href: "..."},
...
]
}

Errors


ERRORS

  • As descriptive as possible
  • As much information as possible
  • FE Developers are your customers

POST /vehicles


409 Conflict
{
"status": 409,
"code": 40985,
"property": "name",
"message": "A vehicle named 'Avengers Sportage' already exists.",
"developersMessage": "A vehicle named 'Avengers Sportage' already exists. If you have a stale local cache, please expire it now.",
"moreInfo": "https://razorfish.attlasian.com/wiki/api/errors/40985"
}

Security

Avoid sessions when possible
  • Authenticate every request if necessary
  • Stateless

Use Existing Protocol:
  • Oauth 1.0a, Oauth2, Basic over SSL ONLY

Custom schemes?

Use API Keys instead of 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 so sorry, you're not allowed!"

HTTP Authentication Schemes

  • Server response to issue challenge:

www-Authenticate: <scheme name>
realm="Application Name"

  • Client request to submit credentials:

Authorization: <scheme name> <data>

API Keys

  • Entropy
  • Password Reset
  • Independence
  • Speed (MD5, SHA1, BCrypt)
  • Limited Exposure
  • Traceability

ID's

  • IDs should be opaque
  • Should be globally unique
  • Avoid Sequential numbers (contention, fusking)
  • Good candidates: UUIDs, "Url64"

HTTP Method Overrides

POST /accounts/x7y8z9?_method=DELETE

Caching


Server (initial response)
ETag: "685584758444e7d744d"

Client (later request):
If-None-Match: "685584758444e7d744d"

Server (later response):
304 Not Modified

Maintenance

Use HTTP Redirects


Create abstraction layer / endpoints when migrating


Use well defined custom Media Types


Visit www.kia.com/us

Thanks

REST Best Practices

By Helbert Rico

REST Best Practices

  • 607