Beautiful REST + JSON APIsLes Hazlewood @lhazlewoodCTO, Stormpathstormpath.com
.com• Identity Management andAccess Control API• Security for your applications• User security workflows• Security best pr...
Outline• APIs, REST & JSON• REST Fundamentals• DesignBase URLVersioningResource FormatReturn ValuesContent NegotiationRefe...
APIs• Applications• Developers• Pragmatism over Ideology• Adoption• Scale
Why REST?• Scalability• Generality• Independence• Latency (Caching)• Security• Encapsulation
Why JSON?• Ubiquity• Simplicity• Readability• Scalability• Flexibility
HATEOAS• Hypermedia• As• The• Engine• Of• Application• StateFurther restriction on REST architectures.
REST Is Easy
REST Is *&@#$! Hard(for providers)
REST can be easy(if you follow some guidelines)
Example Domain: Stormpath• Applications• Directories• Accounts• Groups• Associations• Workflows
Fundamentals
ResourcesNouns, not VerbsCoarse Grained, not Fine GrainedArchitectural style for use-case scalability
What If?/getAccount/createDirectory/updateGroup/verifyAccountEmailAddress
What If?/getAccount/getAllAccounts/searchAccounts/createDirectory/createLdapDirectory/updateGroup/updateGroupName/findGrou...
Keep It Simple
The AnswerFundamentally two types of resources:Collection ResourceInstance Resource
Collection Resource/applications
Instance Resource/applications/a1b2c3
Behavior• GET• PUT• POST• DELETE• HEAD
BehaviorPOST, GET, PUT, DELETE≠ 1:1Create, Read, Update, Delete
BehaviorAs you would expect:GET = ReadDELETE = DeleteHEAD = Headers, no Body
BehaviorNot so obvious:PUT and POST can both be used forCreate and Update
PUT for CreateIdentifier is known by the client:PUT /applications/clientSpecifiedId{…}
PUT for UpdateFull ReplacementPUT /applications/existingId{“name”: “Best App Ever”,“description”: “Awesomeness”}
PUTIdempotent
POST as CreateOn a parent resourcePOST /applications{“name”: “Best App Ever”}Response:201 CreatedLocation: https://api.sto...
POST as UpdateOn instance resourcePOST /applications/a1b2c3{“name”: “Best App Ever. Srsly.”}Response:200 OK
POSTNOT Idempotent
Media Types• Format Specification + Parsing Rules• Request: Accept header• Response: Content-Type header• application/json...
Design Time!
Base URL
http(s)://api.foo.comvshttp://www.foo.com/dev/service/api/rest
http(s)://api.foo.comRest ClientvsBrowser
Versioning
URLhttps://api.stormpath.com/v1vs.Media-Typeapplication/json+foo;application&v=1
Resource Format
Media TypeContent-Type: application/jsonWhen time allows:application/foo+jsonapplication/foo+json;bar=baz&v=1…
camelCase„JS‟ in „JSON‟ = JavaScriptmyArray.forEachNot myArray.for_eachaccount.givenNameNot account.given_nameUnderscores ...
Date/Time/TimestampThere‟s already a standard. Use it: ISO 8601Example:{…,“createdTimestamp”: “2012-07-10T18:02:24.343Z”}U...
HREF• Distributed Hypermedia is paramount!• Every accessible Resource has a uniqueURL.• Replaces IDs (IDs exist, but are o...
Response Body
GET obviousWhat about POST?Return the representation in the responsewhen feasible.Add override (?_body=false) for control
Content Negotiation
Header• Accept header• Header values comma delimited in orderof preferenceGET /applications/a1b2c3Accept: application/json...
Resource Extension/applications/a1b2c3.json/applications/a1b2c3.csv…Conventionally overrides Accept header
Resource Referencesaka „Linking‟
• Hypermedia is paramount.• Linking is fundamental to scalability.• Tricky in JSON• XML has it (XLink), JSON doesn‟t• How ...
Instance ReferenceGET /accounts/x7y8z9200 OK{“href”: “https://api.stormpath.com/v1/accounts/x7y8z9”,“givenName”: “Tony”,“s...
Instance ReferenceGET /accounts/x7y8z9200 OK{“href”: “https://api.stormpath.com/v1/accounts/x7y8z9”,“givenName”: “Tony”,“s...
Collection ReferenceGET /accounts/x7y8z9200 OK{“href”: “https://api.stormpath.com/v1/accounts/x7y8z9”,“givenName”: “Tony”,...
Reference Expansion(aka Entity Expansion, Link Expansion)
Account and its Directory?
GET /accounts/x7y8z9?expand=directory200 OK{“href”: “https://api.stormpath.com/v1/accounts/x7y8z9”,“givenName”: “Tony”,“su...
Partial Representations
GET/accounts/x7y8z9?fields=givenName,surname,directory(name)
Pagination
Collection Resource supports query params:• Offset• Limit…/applications?offset=50&limit=25
GET /accounts/x7y8z9/groups200 OK{“href”: “…/accounts/x7y8z9/groups”,“offset”: 0,“limit”: 25,“first”: { “href”: “…/account...
Many To Many
Group to Account• A group can have many accounts• An account can be in many groups• Each mapping is a resource:GroupMember...
GET /groupMemberships/23lk3j2j3200 OK{“href”: “…/groupMemberships/23lk3j2j3”,“account”: {“href”: “…”},“group”: {“href”: “…...
GET /accounts/x7y8z9200 OK{“href”: “…/accounts/x7y8z9”,“givenName”: “Tony”,“surname”: “Stark”,…,“groups”: {“href”: “…/acco...
Errors
• As descriptive as possible• As much information as possible• Developers are your customers
POST /directories409 Conflict{“status”: 409,“code”: 40924,“property”: “name”,“message”: “A Directory named „Avengers‟alrea...
Security
Avoid sessions when possibleAuthenticate every request if necessaryStatelessAuthorize based on resource content, NOT URL!U...
401 vs 403• 401 “Unauthorized” really meansUnauthenticated“You need valid credentials for me to respond tothis request”• 4...
HTTP Authentication Schemes• Server response to issue challenge:WWW-Authenticate: <scheme name>realm=“Application Name”• C...
API Keys• Entropy• Password Reset• Independence• Speed• Limited Exposure• Traceability
IDs
• IDs should be opaque• Should be globally unique• Avoid sequential numbers(contention, fusking)• Good candidates: UUIDs, ...
HTTP Method Overrides
POST /accounts/x7y8z9?_method=DELETE
Caching &Concurrency Control
Server (initial response):ETag: "686897696a7c876b7e”Client (later request):If-None-Match: "686897696a7c876b7e”Server (late...
Maintenance
Use HTTP RedirectsCreate abstraction layer / endpoints whenmigratingUse well defined custom Media Types
.com• Free for any application• Eliminate months of development• Automatic security best practicesSign Up Now: Stormpath.com
Upcoming SlideShare
Loading in...5
×

Beautiful REST and JSON APIs - Les Hazlewood

953

Published on

Designing a really clean and intuitive REST + JSON API is no small feat. You have to worry about resources, collections of resources, pagination, query parameters, references to other resources, which HTTP Methods to use, HTTP Caching, security, and more! And you have to make sure it lasts and doesn't break clients as you add features over time. Further, while there are many references on creating REST APIs with XML, there are many fewer references for REST + JSON.

Published in: Technology

Beautiful REST and JSON APIs - Les Hazlewood

  1. 1. Beautiful REST + JSON APIsLes Hazlewood @lhazlewoodCTO, Stormpathstormpath.com
  2. 2. .com• Identity Management andAccess Control API• Security for your applications• User security workflows• Security best practices• Developer tools, SDKs, libraries
  3. 3. Outline• APIs, REST & JSON• REST Fundamentals• DesignBase URLVersioningResource FormatReturn ValuesContent NegotiationReferences (Linking)PaginationQuery ParametersAssociationsErrorsIDsMethod OverloadingResource ExpansionPartial ResponsesCaching & EtagsSecurityMulti TenancyMaintenance
  4. 4. APIs• Applications• Developers• Pragmatism over Ideology• Adoption• Scale
  5. 5. Why REST?• Scalability• Generality• Independence• Latency (Caching)• Security• Encapsulation
  6. 6. Why JSON?• Ubiquity• Simplicity• Readability• Scalability• Flexibility
  7. 7. HATEOAS• Hypermedia• As• The• Engine• Of• Application• StateFurther restriction on REST architectures.
  8. 8. REST Is Easy
  9. 9. REST Is *&@#$! Hard(for providers)
  10. 10. REST can be easy(if you follow some guidelines)
  11. 11. Example Domain: Stormpath• Applications• Directories• Accounts• Groups• Associations• Workflows
  12. 12. Fundamentals
  13. 13. ResourcesNouns, not VerbsCoarse Grained, not Fine GrainedArchitectural style for use-case scalability
  14. 14. What If?/getAccount/createDirectory/updateGroup/verifyAccountEmailAddress
  15. 15. What If?/getAccount/getAllAccounts/searchAccounts/createDirectory/createLdapDirectory/updateGroup/updateGroupName/findGroupsByDirectory/searchGroupsByName/verifyAccountEmailAddress/verifyAccountEmailAddressByToken…Smells like bad RPC. DON‟T DO THIS.
  16. 16. Keep It Simple
  17. 17. The AnswerFundamentally two types of resources:Collection ResourceInstance Resource
  18. 18. Collection Resource/applications
  19. 19. Instance Resource/applications/a1b2c3
  20. 20. Behavior• GET• PUT• POST• DELETE• HEAD
  21. 21. BehaviorPOST, GET, PUT, DELETE≠ 1:1Create, Read, Update, Delete
  22. 22. BehaviorAs you would expect:GET = ReadDELETE = DeleteHEAD = Headers, no Body
  23. 23. BehaviorNot so obvious:PUT and POST can both be used forCreate and Update
  24. 24. PUT for CreateIdentifier is known by the client:PUT /applications/clientSpecifiedId{…}
  25. 25. PUT for UpdateFull ReplacementPUT /applications/existingId{“name”: “Best App Ever”,“description”: “Awesomeness”}
  26. 26. PUTIdempotent
  27. 27. POST as CreateOn a parent resourcePOST /applications{“name”: “Best App Ever”}Response:201 CreatedLocation: https://api.stormpath.com/applications/a1b2c3
  28. 28. POST as UpdateOn instance resourcePOST /applications/a1b2c3{“name”: “Best App Ever. Srsly.”}Response:200 OK
  29. 29. POSTNOT Idempotent
  30. 30. Media Types• Format Specification + Parsing Rules• Request: Accept header• Response: Content-Type header• application/json• application/foo+json• application/foo+json;application• …
  31. 31. Design Time!
  32. 32. Base URL
  33. 33. http(s)://api.foo.comvshttp://www.foo.com/dev/service/api/rest
  34. 34. http(s)://api.foo.comRest ClientvsBrowser
  35. 35. Versioning
  36. 36. URLhttps://api.stormpath.com/v1vs.Media-Typeapplication/json+foo;application&v=1
  37. 37. Resource Format
  38. 38. Media TypeContent-Type: application/jsonWhen time allows:application/foo+jsonapplication/foo+json;bar=baz&v=1…
  39. 39. camelCase„JS‟ in „JSON‟ = JavaScriptmyArray.forEachNot myArray.for_eachaccount.givenNameNot account.given_nameUnderscores for property/function names areunconventional for JS. Stay consistent.
  40. 40. Date/Time/TimestampThere‟s already a standard. Use it: ISO 8601Example:{…,“createdTimestamp”: “2012-07-10T18:02:24.343Z”}Use UTC!
  41. 41. HREF• Distributed Hypermedia is paramount!• Every accessible Resource has a uniqueURL.• Replaces IDs (IDs exist, but are opaque).{“href”: https://api.stormpath.com/v1/accounts/x7y8z9”,…}Critical for linking, as we‟ll soon see
  42. 42. Response Body
  43. 43. GET obviousWhat about POST?Return the representation in the responsewhen feasible.Add override (?_body=false) for control
  44. 44. Content Negotiation
  45. 45. Header• Accept header• Header values comma delimited in orderof preferenceGET /applications/a1b2c3Accept: application/json, text/plain
  46. 46. Resource Extension/applications/a1b2c3.json/applications/a1b2c3.csv…Conventionally overrides Accept header
  47. 47. Resource Referencesaka „Linking‟
  48. 48. • Hypermedia is paramount.• Linking is fundamental to scalability.• Tricky in JSON• XML has it (XLink), JSON doesn‟t• How do we do it?
  49. 49. Instance ReferenceGET /accounts/x7y8z9200 OK{“href”: “https://api.stormpath.com/v1/accounts/x7y8z9”,“givenName”: “Tony”,“surname”: “Stark”,…,“directory”: ????}
  50. 50. Instance ReferenceGET /accounts/x7y8z9200 OK{“href”: “https://api.stormpath.com/v1/accounts/x7y8z9”,“givenName”: “Tony”,“surname”: “Stark”,…,“directory”: {“href”: “https://api.stormpath.com/v1/directories/g4h5i6”}}
  51. 51. Collection ReferenceGET /accounts/x7y8z9200 OK{“href”: “https://api.stormpath.com/v1/accounts/x7y8z9”,“givenName”: “Tony”,“surname”: “Stark”,…,“groups”: {“href”: “https://api.stormpath.com/v1/accounts/x7y8z9/groups”}}
  52. 52. Reference Expansion(aka Entity Expansion, Link Expansion)
  53. 53. Account and its Directory?
  54. 54. GET /accounts/x7y8z9?expand=directory200 OK{“href”: “https://api.stormpath.com/v1/accounts/x7y8z9”,“givenName”: “Tony”,“surname”: “Stark”,…,“directory”: {“href”: “https://api.stormpath.com/v1/directories/g4h5i6”,“name”: “Avengers”,“creationDate”: “2012-07-01T14:22:18.029Z”,…}}
  55. 55. Partial Representations
  56. 56. GET/accounts/x7y8z9?fields=givenName,surname,directory(name)
  57. 57. Pagination
  58. 58. Collection Resource supports query params:• Offset• Limit…/applications?offset=50&limit=25
  59. 59. GET /accounts/x7y8z9/groups200 OK{“href”: “…/accounts/x7y8z9/groups”,“offset”: 0,“limit”: 25,“first”: { “href”: “…/accounts/x7y8z9/groups?offset=0” },“previous”: null,“next”: { “href”: “…/accounts/x7y8z9/groups?offset=25” },“last”: { “href”: “…”},“items”: [{“href”: “…”},{“href”: “…”},…]}
  60. 60. Many To Many
  61. 61. Group to Account• A group can have many accounts• An account can be in many groups• Each mapping is a resource:GroupMembership
  62. 62. GET /groupMemberships/23lk3j2j3200 OK{“href”: “…/groupMemberships/23lk3j2j3”,“account”: {“href”: “…”},“group”: {“href”: “…”},…}
  63. 63. GET /accounts/x7y8z9200 OK{“href”: “…/accounts/x7y8z9”,“givenName”: “Tony”,“surname”: “Stark”,…,“groups”: {“href”: “…/accounts/x7y8z9/groups”},“groupMemberships”: {“href”: “…/groupMemberships?accountId=x7y8z9”}}
  64. 64. Errors
  65. 65. • As descriptive as possible• As much information as possible• Developers are your customers
  66. 66. POST /directories409 Conflict{“status”: 409,“code”: 40924,“property”: “name”,“message”: “A Directory named „Avengers‟already exists.”,“developerMessage”: “A directory named„Avengers‟ already exists. If you have a stalelocal cache, please expire it now.”,“moreInfo”:“https://www.stormpath.com/docs/api/errors/40924”}
  67. 67. Security
  68. 68. Avoid sessions when possibleAuthenticate every request if necessaryStatelessAuthorize based on resource content, NOT URL!Use Existing Protocol:Oauth 1.0a, Oauth2, Basic over SSL onlyCustom Authentication Scheme:Only if you provide client code / SDKOnly if you really, really know what you‟re doingUse API Keys instead of Username/Passwords
  69. 69. 401 vs 403• 401 “Unauthorized” really meansUnauthenticated“You need valid credentials for me to respond tothis request”• 403 “Forbidden” really means Unauthorized“I understood your credentials, but so sorry, you‟renot allowed!”
  70. 70. HTTP Authentication Schemes• Server response to issue challenge:WWW-Authenticate: <scheme name>realm=“Application Name”• Client request to submit credentials:Authorization: <scheme name> <data>
  71. 71. API Keys• Entropy• Password Reset• Independence• Speed• Limited Exposure• Traceability
  72. 72. IDs
  73. 73. • IDs should be opaque• Should be globally unique• Avoid sequential numbers(contention, fusking)• Good candidates: UUIDs, „Url64‟
  74. 74. HTTP Method Overrides
  75. 75. POST /accounts/x7y8z9?_method=DELETE
  76. 76. Caching &Concurrency Control
  77. 77. Server (initial response):ETag: "686897696a7c876b7e”Client (later request):If-None-Match: "686897696a7c876b7e”Server (later response):304 Not Modified
  78. 78. Maintenance
  79. 79. Use HTTP RedirectsCreate abstraction layer / endpoints whenmigratingUse well defined custom Media Types
  80. 80. .com• Free for any application• Eliminate months of development• Automatic security best practicesSign Up Now: Stormpath.com

×