Building Great APIs (Midwest PHP 2016)

Building Great APIs (Midwest PHP 2016)

Much focus has been spent discussing the merits of RESTful APIs. Sure, REST is important, but how do we put these concepts into practice and build great APIs? How RESTful do we need to be, and where do we draw the line with a pragmatic approach to ship code and make our users happy? In this talk, I'll show how to build APIs that put into practice the concepts of REST, while showing that it's okay to bend or break the rules. Along the way, we'll cover the Richardson Maturity Model, hypermedia, when and where to use content negotiation, and API versioning pitfalls


Ben Ramsey

March 04, 2016


  1. 2.

    HI, I’M BEN. I’m a web craftsman, author, and speaker.

    I build a platform for professional photographers at ShootProof. I enjoy APIs, open source software, organizing user groups, good beer, and spending time with my family. Nashville, TN is my home. ▸Zend PHP Certification Study Guide ▸Nashville PHP & Atlanta PHP user groups ▸array_column() ▸ramsey/uuid ▸league/oauth2-client
  2. 3.
  3. 4.
  4. 8.

    It’s as if we wanted to invent a way for

    there to be someone wrong on the Internet.
  5. 11.

    BUILDING GREAT APIs Scenario One: “Our client has an ad

    campaign, and they want to capture email addresses and postal codes and report on that information. We need to give them an endpoint to post data and another endpoint to pull the list. “The campaign will only last a month. We won’t need it after that.”
  6. 12.

    BUILDING GREAT APIs Scenario One: Break Down ▸ Action 1:

    collect email address and postal code ▸ Action 2: return list of email addresses and postal codes ▸ Only needed for a month
  7. 13.

    BUILDING GREAT APIs Possible Solution ▸ RPC (remote procedure call)

    ▸ saveCampaignData ▸ email: ▸ postalCode: 12345 ▸ getCampaignData
  8. 14.

    POST /rpc.php HTTP/1.1 Host: Content-Length: 125 { "method": "saveCampaignData",

    "params": { "email": "", "postalCode": "12345" } } HTTP/1.1 200 OK Date: Sat, 10 Oct 2015 05:26:24 GMT Content-Length: 23 { "status": "success" }
  9. 16.

    BUILDING GREAT APIs Scenario Two: “Our client has decided they

    want to run the campaign a bit longer, and they also want to run a second campaign like it, but this time, they want to collect first and last names. “Let’s just take that API you wrote and re-purpose it.”
  10. 17.

    BUILDING GREAT APIs Scenario Two: Caveats ▸ It was specific

    to a single campaign ▸ It only collected email address and postal code ▸ The code is simple enough to make these changes and not break the existing campaign, but will we need to support more changes?
  11. 20.

    BUILDING GREAT APIs Better Solution Using Resources ▸ Each submission

    creates a new record in the campaign ▸ Resources: campaign, record ▸ Ability to create a record in a campaign ▸ Ability to fetch all records in a campaign
  12. 21.

    HTTP/1.1 200 OK Date: Sat, 10 Oct 2015 16:31:57 GMT

    Content-Length: 23 { "status": "success" } GET /record/create?campaignID=o0zJeEcJ & &postalCode=12345 HTTP/1.1 Host:
  13. 22.

    HTTP/1.1 200 OK Date: Sat, 10 Oct 2015 16:43:23 GMT

    Content-Length: 120 { "records": [{ "email": "", "postalCode": "12345" }] } GET /campaign/o0zJeEcJ HTTP/1.1 Host:
  14. 25.

    BUILDING GREAT APIs Better Solution Using HTTP Verbs ▸ Use

    proper HTTP semantics for resources ▸ POST to create a new record, GET to fetch a campaign ▸ What else can we do with that campaign resource? ▸ Create new campaigns with POST ▸ Update campaigns with PUT ▸ What about the record? ▸ Fetch an individual record with GET
  15. 26.

    HTTP/1.1 200 OK Date: Sat, 10 Oct 2015 17:14:11 GMT

    Content-Length: 67 { "campaignId": "o0zJeEcJ", "name": "My Awesome Campaign" } POST /campaign HTTP/1.1 Host: Content-Length: 37 { "name": "My Awesome Campaign" }
  16. 27.

    HTTP/1.1 200 OK Date: Sat, 10 Oct 2015 17:22:04 GMT

    Content-Length: 173 { "campaignId": "o0zJeEcJ", "name": "My Awesome Campaign", "startDate": "2015-09-06T18:12:47+00:00", "endDate": "2015-10-06T18:13:06+00:00" } PUT /campaign/o0zJeEcJ HTTP/1.1 Host: Content-Length: 143 { "name": "My Awesome Campaign", "startDate": "2015-09-06T18:12:47+00:00", "endDate": "2015-10-06T18:13:06+00:00" }
  17. 28.

    HTTP/1.1 200 OK Date: Sat, 10 Oct 2015 17:28:45 GMT

    Content-Length: 119 { "recordId": "5s7ytJlT", "campaignID": "o0zJeEcJ", "email": "", "postalCode": "12345" } POST /record HTTP/1.1 Host: Content-Length: 91 { "campaignID": "o0zJeEcJ", "email": "", "postalCode": "12345" }
  18. 29.

    HTTP/1.1 200 OK Date: Sat, 10 Oct 2015 17:29:31 GMT

    Content-Length: 119 { "recordId": "5s7ytJlT", "campaignID": "o0zJeEcJ", "email": "", "postalCode": "12345" } GET /record/5s7ytJlT HTTP/1.1 Host:
  19. 31.

    Level 0: The Swamp of Pox Level 1: Resources Level

    2: HTTP Verbs Richardson Maturity Model
  20. 33.

    BUILDING GREAT APIs Scenario Three: “Our client likes using our

    API. They want to know if they can use it to create and manage an indefinite amount of campaigns. Oh—I almost forgot—we have another client who wants to use our API to manage campaigns, too. Can we do that?” Why, yes! We can.
  21. 34.

    BUILDING GREAT APIs Scenario Three: Considerations ▸ Authentication ▸ Stateless

    authentication with keys, tokens, & shared secrets ▸ As more clients want access to your API, how do they know what the endpoints are? ▸ Up until now, it’s through documentation ▸ Is there a better way?
  22. 36.

    BUILDING GREAT APIs Better Solution Using Hypermedia ▸ Use links

    to provide relationships between resources ▸ Use media types to describe how to process a representation
  23. 37.

    HTTP/1.1 201 Created Date: Sat, 10 Oct 2015 17:45:27 GMT

    Content-Type: application/hal+json Content-Length: 838 Location: /campaign/o0zJeEcJ POST /campaign HTTP/1.1 Host: Accept: application/hal+json Content-Type: application/json Content-Length: 37 { "name": "My Awesome Campaign" }
  24. 38.

    { "_links": { "self": { "href": "" }, "curies": [{

    "name": "ex", "href": "{rel}", "templated": true }], "ex:campaigns": { "href": "" }, "ex:records": { "href": "" } }, "name": "My Awesome Campaign", "_embedded": { "ex:record": [{ "_links": { "self": { "href": "" }, "ex:campaign": { "href": "" } }, "email": "", "postalCode": "12345" }] } }
  25. 39.

    Level 0: The Swamp of Pox Level 1: Resources Level

    2: HTTP Verbs Level 3: Hypermedia Richardson Maturity Model
  26. 44.

    BUILDING GREAT APIs Evolvability ▸ If we change the URLs,

    will we break clients? ▸ If we add or remove properties, will we break clients? ▸ If we change the input values we accept, will we break clients?
  27. 48.

    BUILDING GREAT APIs Versioning and Content Negotiation ▸ I can

    change URLs and nothing breaks ▸ I can add properties and nothing breaks ▸ If I remove properties or require new inputs, I can use content- negotiation to version the API: application/vnd.example+json;version=2 ▸ Everybody’s happy
  28. 51.

    Roy T. Fielding If you think you have control over

    the system or aren’t interested in evolvability, don’t waste your time arguing about REST. BUILDING GREAT APIs
  29. 52.

    THANK YOU. ANY QUESTIONS? If you want to talk more,

    feel free to contact me. @ramsey Building Great APIs Copyright © 2016 Ben Ramsey This work is licensed under Creative Commons Attribution- ShareAlike 4.0 International. For uses not covered under this license, please contact the author. Ramsey, Ben. “Building Great APIs.” Midwest PHP Conference. Hilton Minneapolis, Minneapolis. 4 Mar. 2016. Conference presentation. This presentation was created using Keynote. The text is set in Chunk Five and Helvetica Neue. The source code is set in Menlo. The iconography is provided by Font Awesome. Unless otherwise noted, all photographs are used by permission under a Creative Commons license. Please refer to the Photo Credits slide for more information. Ŏ
  30. 53.

    PHOTO CREDITS 1. “Sunset at Mid State Fair” by Howard

    Ignatius 2. Photo of Ben Ramsey by Eli White 3. “Magical Merry Go Round” by Floyd Stewart 4. “Dusk Lemonade Stand” by Kellar Wilson 5. “Duty Calls” by Randall Monroe, XKCD 6. “The Wheel” by Aristocrats-hat 7. “Playing With My New Camera” by Mark Walley 8. “Merry-go-round” by mafleen 9. “Caramel Apples” by m01229 10.“Elephant Ears + spinning ride” by m01229 11.“Garden of Unearthly Delights - sideshows” by Heather 12.“The Magnificent Machines of Yeserday (House on the Rock)” by Justin Kern 13.“Mr. Dark’s Ticket Booth (House on the Rock)” by Justin Kern 14.“The Ultimate Carnival” by Trey Ratcliff 15.“Marenghi Orchestrion at the Great Dorset Steam Fair” by Anguskirk 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15