Usable Rest APIs by Javier Ramirez at London Ruby User Group

42
{"links":[ {"rel":"author", "uri":"http://javier-ramirez.com"}, {"rel":"work", "uri":"http://aspgems.com"}, {"rel":"blog", "uri":"http://formatinternet.com"}, {"rel":"twittEr", "uri":"http//twitter.com/supercoco9 "} ]} usable REST APIs

description

With the adoption of REST, the proliferation of smartphones and tablets, and the second coming of JavaScript, exposing our applications as a service is now more important than ever. Rails or Sinatra make really easy to create a (kinda) RESTful API but, in many occassions, these APIs are designed without really thinking on the developers that will have to use them. I want to talk about some of the points that can help making your API more developer-friendly. Some of the areas I’ll cover will be discoverability, authentication, headers, formats, parameters, documentation and tools. Talk delivered at London Ruby User Group on 12/12/2011

Transcript of Usable Rest APIs by Javier Ramirez at London Ruby User Group

Page 1: Usable Rest APIs by Javier Ramirez at London Ruby User Group

{"links":[ {"rel":"author", "uri":"http://javier-ramirez.com"}, {"rel":"work", "uri":"http://aspgems.com"}, {"rel":"blog", "uri":"http://formatinternet.com"}, {"rel":"twittEr", "uri":"http//twitter.com/supercoco9"}]}

usable REST APIs

Page 2: Usable Rest APIs by Javier Ramirez at London Ruby User Group

1996

Page 3: Usable Rest APIs by Javier Ramirez at London Ruby User Group

1995

Page 4: Usable Rest APIs by Javier Ramirez at London Ruby User Group

1996

Page 5: Usable Rest APIs by Javier Ramirez at London Ruby User Group

1994

Page 6: Usable Rest APIs by Javier Ramirez at London Ruby User Group

2001

Page 7: Usable Rest APIs by Javier Ramirez at London Ruby User Group

1999

Page 8: Usable Rest APIs by Javier Ramirez at London Ruby User Group

2004

Page 9: Usable Rest APIs by Javier Ramirez at London Ruby User Group

Web usability is an approach to make web sites

easy to use for an end-user, without the requirement that any specialized training be

undertaken.[]

Page 10: Usable Rest APIs by Javier Ramirez at London Ruby User Group

LearnabilityEfficIeNcyMemorabiliTyErrorsSatisfActiOn

Page 11: Usable Rest APIs by Javier Ramirez at London Ruby User Group

I want YOUto make

a (REST) API

Page 12: Usable Rest APIs by Javier Ramirez at London Ruby User Group

REST in a nutshell

client server stateless layered and cacheable

Resources

Resource Identifiers

Resource metadata

Uniform interface

operations

Representations

Representation metadata

Optionally: code on demand

Page 13: Usable Rest APIs by Javier Ramirez at London Ruby User Group

easy

making a REST API

with Rails?

Page 14: Usable Rest APIs by Javier Ramirez at London Ruby User Group

BASIC

WEB/api

functionality

IN RAILS

Page 15: Usable Rest APIs by Javier Ramirez at London Ruby User Group

Cohesion

pleasE

Page 16: Usable Rest APIs by Javier Ramirez at London Ruby User Group

separation of concerns

Page 17: Usable Rest APIs by Javier Ramirez at London Ruby User Group

SUCCESS consistently

fail consistently

Page 18: Usable Rest APIs by Javier Ramirez at London Ruby User Group

expose ONLY

WHAT ISStrictly

necessary

Page 19: Usable Rest APIs by Javier Ramirez at London Ruby User Group

resources are not models

Page 20: Usable Rest APIs by Javier Ramirez at London Ruby User Group

Aggregation/

composition

Multiple

representations

Page 21: Usable Rest APIs by Javier Ramirez at London Ruby User Group

Multiple consumers

Page 22: Usable Rest APIs by Javier Ramirez at London Ruby User Group

All your

FORMAT

are belong

to us

Page 23: Usable Rest APIs by Javier Ramirez at London Ruby User Group

> curl -d "[email protected]&password=keepdreaming" https://invoicefu.com/api/session?format=json

{"user":{"id":108,"name":"Nicolas Carroll","email":"[email protected]","locale":"en","twitter_nickname":null,"facebook_uid":null,"facebook_nickname":null,"api_key":"dbd349b30b6d9fde97b01b827e6be5ed1e4fbe72","links":[{"rel":"session","uri":"https://invoicefu.com/api/session","methods":"GET,POST,DESTROY"},"rel":"account","uri":"https://invoicefu.com/api/accounts/108-cole-mertz-fake","methods":"GET,PUT"},"rel":"clients","uri":"https://invoicefu.com/api/accounts/108-cole-mertz-fake/clients","methods":"GET,POST"},{"rel":"new_client","uri":"https://invoicefu.com/api/accounts/108-cole-mertz-fake/clients/new","methods":"GET"},{"rel":"invoices","uri":"https://invoicefu.com/api/accounts/108-cole-mertz-fake/invoices","methods":"GET,POST"},{"rel":"new_invoice","uri":"https://invoicefu.com/api/accounts/108-cole-mertz-fake/invoices/new","methods":"GET"},{"rel":"proformas","uri":"https://invoicefu.com/api/accounts/108-cole-mertz-fake/proformas","methods":"GET,POST"},{"rel":"new_proforma","uri":"https://invoicefu.com/api/accounts/108-cole-mertz-fake/proformas/new","methods":"GET"}]}}

Page 24: Usable Rest APIs by Javier Ramirez at London Ruby User Group

> curl -d "[email protected]&password=yeahyeah" "https://invoicefu.com/api/session?&format=xml

<?xml version="1.0" encoding="UTF-8"?><user> <id>108</id> <name>Nicolas Carroll</name> <email>[email protected]</email> <locale>en</locale> <twitter-nickname nil="true"></twitter-nickname> <facebook-uid nil="true"></facebook-uid> <facebook-nickname nil="true"></facebook-nickname> <api-key>dbd349b30b6d9fde97b01b827e6be5ed1e4fbe72</api-key> <links> <link> <rel>session</rel> <uri>https://invoicefu-localhost.com/api/session</uri> <methods>GET,POST,DESTROY</methods> </link> <link> <rel>account</rel> <uri>https://invoicefu-localhost.com/api/accounts/108-cole-mertz-fake</uri> <methods>GET,PUT</methods> </link> <link> <rel>clients</rel> <uri>https://invoicefu-localhost.com/api/accounts/108-cole-mertz-fake/clients</uri> <methods>GET,POST</methods> </link> <link> <rel>new_client</rel> <uri>https://invoicefu-localhost.com/api/accounts/108-cole-mertz-fake/clients/new</uri> <methods>GET</methods> </link> (…)

</links></user>

can I haz cat readable anzwa

Page 25: Usable Rest APIs by Javier Ramirez at London Ruby User Group

THE

ACCEPT

HEADER

HTTP/REST StandardUnambiguousResources != RepresentationsVersion as you need it

Not everyone supports headers or custom types

Less obviousHarder to useNon standard content-typesSkips HTTP server logs

Accept: application/vnd.aspgems.invoicefu.v1.xml

Page 26: Usable Rest APIs by Javier Ramirez at London Ruby User Group
Page 27: Usable Rest APIs by Javier Ramirez at London Ruby User Group
Page 28: Usable Rest APIs by Javier Ramirez at London Ruby User Group

templates

for new

resources> curl "https://invoicefu.com/api/v1/accounts/108-cole-mertz-fake/invoices/new?api_key=ddd349b30b6d9fde97b01b827e6be5ed1e4fbe72&format=json"

{"invoice":{"number":"2011/30","issued_on":"2011-12-12","proforma_id":null,"notes":null,"footer":null,"locale":"en","currency_code":"USD","currency_symbol":"$","ac_name":"Cole-Mertz#FAKE","ac_company_number_name":"Company number","ac_company_number":"25465828K","ac_tax_number_name":"VAT Number","ac_tax_number":"ES25464828k","ac_address":"234 brecknock road","ac_city":"london","ac_province":null,"ac_postal_code":"n18 5bq","ac_country_name":"United Kingdom","cl_email":null,"cl_name":null,"cl_company_number_name":null,"cl_company_number":null,"cl_tax_number_name":null,"cl_tax_number":null,"cl_address":null,"cl_city":null,"cl_province":null,"cl_postal_code":null,"cl_country_name":null,"invoice_lines":[],"discount_percent":null,"tax_lines":[{"name":"TVA","signed_percent":"19.6"}],"paid":"0.0","links":[{"rel":"payments","uri":"https://invoicefu.com/api/accounts/108-cole-mertz-fake/invoices//payments","methods":"POST"},{"rel":"account","uri":"https://invoicefu.com/api/accounts/108-cole-mertz-fake","methods":"GET,PUT"},{"rel":"client","uri":null,"methods":"GET,PUT,DELETE"},{"rel":"proforma","uri":null,"methods":"GET,PUT,DELETE"},{"rel":"pdf","uri":null,"methods":"GET"},{"rel":"invoices","uri":"https://invoicefu.com/api/accounts/108-cole-mertz-fake/invoices","methods":"GET,POST"}]}}j

Page 29: Usable Rest APIs by Javier Ramirez at London Ruby User Group

EASy

To

FIND

Page 30: Usable Rest APIs by Javier Ramirez at London Ruby User Group

> curl https://invoicefu.com?format=xml (or curl -H "Accept: application/xml" "https://invoicefu.com")

<?xml version="1.0" encoding="UTF-8"?><invoicefu> <links> <link> <rel>session</rel> <uri>https://invoicefu.com/api/session</uri> <methods>POST.DELETE</methods> </link> <link> <rel>countries</rel> <uri>https://invoicefu.com/api/countries</uri> <methods>GET</methods> </link> <link> <rel>api_v1</rel> <uri>https://invoicefu.com/api/session?api_version=1</uri> <methods>POST.DELETE</methods> </link> <link> <rel>xml_representation</rel> <uri>https://invoicefu.com/api/session?format=xml</uri> <methods>POST.DELETE</methods> </link> <link> <rel>json_representation</rel> <uri>https://invoicefu.com/api/session?format=json</uri> <methods>POST.DELETE</methods> </link> <link> <rel>strict_parameters</rel> <uri>https://invoicefu.com/api/session?strict=true</uri> <methods>POST.DELETE</methods> </link> </links></invoicefu>

Page 31: Usable Rest APIs by Javier Ramirez at London Ruby User Group

> curl -d "[email protected]&password=yeahyeah" "https://invoicefu.com/api/session?&format=xml

<?xml version="1.0" encoding="UTF-8"?><user> <id>108</id> <name>Nicolas Carroll</name> <email>[email protected]</email> <locale>en</locale> <twitter-nickname nil="true"></twitter-nickname> <facebook-uid nil="true"></facebook-uid> <facebook-nickname nil="true"></facebook-nickname> <api-key>dbd349b30b6d9fde97b01b827e6be5ed1e4fbe72</api-key> <links> <link> <rel>session</rel> <uri>https://invoicefu-localhost.com/api/session</uri> <methods>GET,POST,DESTROY</methods> </link> <link> <rel>account</rel> <uri>https://invoicefu-localhost.com/api/accounts/108-cole-mertz-fake</uri> <methods>GET,PUT</methods> </link> <link> <rel>clients</rel> <uri>https://invoicefu-localhost.com/api/accounts/108-cole-mertz-fake/clients</uri> <methods>GET,POST</methods> </link> <link> <rel>new_client</rel> <uri>https://invoicefu-localhost.com/api/accounts/108-cole-mertz-fake/clients/new</uri> <methods>GET</methods> </link> <link> <rel>invoices</rel> <uri>https://invoicefu-localhost.com/api/accounts/108-cole-mertz-fake/invoices</uri> <methods>GET,POST</methods> </link>

(…)

</links></user>

Page 32: Usable Rest APIs by Javier Ramirez at London Ruby User Group

BASIC ACCESS AUTHENTICATION

OAUTH

TOKEN

authenticate_or_request_with_http_basic do |login, password| User.find_by_login_and_password login, password endUser and password must be passed every time

Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join ) User.find_by_login_and_api_key( params[:login], params[:api_key] ) Client can send it as a parameter or as a header

Depends on third party libraries Requires initial registration of client and more integration

Page 33: Usable Rest APIs by Javier Ramirez at London Ruby User Group

Performance

Page 34: Usable Rest APIs by Javier Ramirez at London Ruby User Group

params &

debug

Page 35: Usable Rest APIs by Javier Ramirez at London Ruby User Group

> curl "https://invoicefu.com/api/accounts/108-cole-mertz-fake/invoices/new?api_key=ddd349b30b6d9fde97b01b827e6be5ed1e4fbe72&format=xml&debug=1"

<?xml version="1.0" encoding="UTF-8"?><errors> <error>extra params found: debug. Allowed params are: account_id,client_id,invoice_id,proforma_id</error></errors>

> curl "https://invoicefu.com/api/accounts/108-cole-mertz-fake/invoices/new?api_key=ddd349b30b6d9fde97b01b827e6be5ed1e4fbe72&format=xml&debug=1&strict=false"<?xml version="1.0" encoding="UTF-8"?><invoice> <number>2011/30</number> <issued-on>2011-12-11</issued-on> <proforma-id nil="true"></proforma-id> (...) <links> (...) <link> <rel>invoices</rel> <uri>https://invoicefu-localhost.com/api/accounts/108-cole-mertz-fake/invoices</uri> <methods>GET,POST</methods> </link> </links></invoice>

Page 36: Usable Rest APIs by Javier Ramirez at London Ruby User Group
Page 37: Usable Rest APIs by Javier Ramirez at London Ruby User Group

WADLjson schema

helping

your users

Page 38: Usable Rest APIs by Javier Ramirez at London Ruby User Group

{ "name":"Product", "properties":{ "id":{ "type":"number", "description":"Product identifier", "required":true }, "name":{ "description":"Name of the product", "type":"string", "required":true }, "price":{ "required":true, "type": "number", "minimum":0, "required":true }, "tags":{ "type":"array", "items":{ "type":"string" } } }, "links":[ { "rel":"full", "href":"{id}" }, { "rel":"comments", "href":"comments/?id={id}" } ] }

<?xml version="1.0"?> <application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://wadl.dev.java.net/2009/02 wadl.xsd" xmlns:tns="urn:yahoo:yn" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:yn="urn:yahoo:yn" xmlns:ya="urn:yahoo:api" xmlns="http://wadl.dev.java.net/2009/02"> <grammars> <include href="NewsSearchResponse.xsd"/> <include href="Error.xsd"/> </grammars> <resources base="http://api.search.yahoo.com/NewsSearchService/V1/"> <resource path="newsSearch"> <method name="GET" id="search"> <request> <param name="appid" type="xsd:string" style="query" required="true"/> 22 <param name="type" style="query" default="all"> <option value="all"/> <option value="any"/> <option value="phrase"/> </param> <param name="start" style="query" type="xsd:int" default="1"/> <param name="language" style="query" type="xsd:string"/> </request> <response status="200"> <representation mediaType="application/xml" element="yn:ResultSet"/> </response> <response status="400"> <representation mediaType="application/xml" element="ya:Error"/> </response> </method> </resource> </resources> </application>

Page 39: Usable Rest APIs by Javier Ramirez at London Ruby User Group

tools

curl

Hurl

Httparty

restclient

Page 40: Usable Rest APIs by Javier Ramirez at London Ruby User Group

apigee

Page 41: Usable Rest APIs by Javier Ramirez at London Ruby User Group
Page 42: Usable Rest APIs by Javier Ramirez at London Ruby User Group