API Tips From the Frontline

API Tips From the Frontline

Starting to write an API is an easy task, but you quickly stumble upon many obstacles and hard decisions. How to manage result pagination? How to handle write operations, file uploads and authentication? Join me as I share my tricks that allowed me to ship high-profile projects in record time while keeping the code clean and maintainable.

B3b2139e4f2c0eca4efe2379fcebc1c5?s=128

Anna Filina

May 29, 2016
Tweet

Transcript

  1. foolab.ca | @foolabca API Tips From the Frontline CakeFest, Amsterdam

    - May 29, 2016
  2. What you will learn • Don't repeat my mistakes. •

    Overcome common obstacles. • Build elegant & pragmatic APIs. 2
  3. Anna Filina • Developer • Problem solver • Teacher •

    Advisor • FooLab + ConFoo 3
  4. Endpoints What URL to call when

  5. Lists • /products • /products?category=games,movies&lang=fr • /products?include=platforms • /products?sort=-date •

    /products?pageNumber=2&pageSize=100 • /products?lang=fr 5
  6. Details • /products/1 • /products/1?include=photos,reviews 6

  7. Response example: list { "data": [ {"name": "Skyrim"}, {"name": "Civilization

    V"} ], "meta": { "pages": 50, "count": 100 } } 7
  8. Write • POST /products • PUT or PATCH /products/1 •

    DELETE /products/1 • Version your APIs: ◦ api.example.org/v1/products ◦ Headers also an option. 8
  9. Request/Response How to format stuff

  10. Multiple formats • xml, json, html, etc. • Can use

    1 endpoint with Accept header. • Can send version, pagination & language info using headers too. 10
  11. Request • Bad: Content-Type: application/x-www-form-urlencoded • Content-Type: application/json • Send

    content (POST) in body, not headers. 11
  12. Request example: add POST /products HTTP/1.1 Host: api.example.org Content-Type: application/json;

    charset=UTF-8 { "data": { "name": "Overwatch", "price": 49.99 } } 12
  13. 3 queries • Count query with same filters but no

    pagination. • Query with filters, pagination & sorting, but only select DISTINCT IDs ◦ Because JOIN + LIMIT = wrong number of root records. • Query all the data (with joins) by IDs and apply sorting. 13
  14. LIMIT SELECT * FROM game LEFT JOIN rating ON rating.game_id

    = game.id WHERE game.price < 20 LIMIT 2; 14 id name price game_id score 1 Skyrim 19.99 1 9.1 1 Skyrim 19.99 1 8.9 2 Diablo 3 17.89 2 8.5
  15. Upload • Use tools, don't DIY (do it yourself). •

    Client-side: Plupload ◦ Step 1: upload temp file. ◦ Step 2: give file path to next API request. • Server-side: Guzzle. 15
  16. Plupload var uploader = new plupload.Uploader({ runtimes: 'html5,html4', max_file_size: '5mb',

    url: '/api/upload', filters: [{extensions: 'jpg,png,jpeg'}] }); uploader.init(); 16
  17. Guzzle <?php $url = 'http://example.org/profiles/1/edit'; $request = $client->createRequest('PATCH', $url); $reqBody

    = $request->getBody(); $reqBody->setField('data', ['first_name':'Anna']); $file = new PostFile('i.jpg', fopen('/path', 'r')); $reqBody->addFile($file); $response = $client->send($request); 17
  18. Status codes • Don't confuse API & HTTP codes. •

    2xx success. • 3xx redirect. • 4xx client error. • 5xx server error. • Send API-specific code in body. 18
  19. Example of error Status: 400 Content-Type: application/json; charset=UTF-8 { "errors":

    {[ "code": 1001, "message": "Price must be greater than 0." ]} } 19
  20. Testing Simpler than you think

  21. HTTP • Use Guzzle or built-in framework tools. • Generate

    HTTP request , compare output. 21
  22. Guzzle test // tests/ApiProductTest.php public function test_GetOneProduct_ReturnsSuccess() { $client =

    new Client(); $response = $client->get('http://example.org/products/1', [ 'exceptions' => false, 'headers' => ['Accept' => 'application/json'] ]); $this->assertEquals(200, $response->getStatusCode()); // ... } 22
  23. Guzzle test // ... $body = $response->getBody()->getContents(); $this->assertJsonStringEqualsJsonString('{ "data": {

    "id": "1", "name": "Overwatch", "price": 49.99 } }', $body); 23
  24. Testing tips • Create separate database for tests. • Write

    tests before you code: ◦ TDD. ◦ Contract between you and client dev. 24
  25. Authentication Nooo, I hate that part! Someone else code it

    plz.
  26. Multiple methods • Don't send username/password in each request. ◦

    Especially with untrusted 3rd parties. ◦ Especially if no SSL. • You can have multiple auth methods for one API. • Sessions similar to tokens. 26
  27. OAuth2 • SSL required (can be risky). • Advanced features

    like access scope. • Can be overkill if you need basic features. • Private credentials. 27
  28. OAuth2 - conceptual diagram 28 User Client app
 (php/js/mobile) API

    Request Forward Validate Create token Store token Login form Login token user/pass
  29. Digest • Its own encryption. • Easy to implement. •

    No replay (nonce). • Comes out-of-the-box with some frameworks. 29
  30. Digest - conceptual diagram 30 Request User/client app API Unauthorized

    Validate digest (nonce,pass) Request nonce
  31. Refactoring to API Don't rewrite all the legacy at once

  32. Progressive rewrite • Rewrite one component/module at a time. •

    Start with whatever has fewer dependencies (or critical). • Delete dead code. • Copy production data for dev environment. 32
  33. Services vs libraries • Services: GET /products?id=1,5 • Libraries /classes:

    • Bypass HTTP • $table->getApiList( ["id" => "1,5"] ); • Can fetch without pagination. • Can hydrate to models. 33
  34. Implementation tips Is this a good way to code it?

  35. Libraries • I use MVC frameworks & ORMs. • Aside

    from frameworks, I prefer small tools that don't do too much magic. 35
  36. Reuse & standardize • Goal: streamline endpoint creation. • Automate

    request parameters extraction. • Automate query generation based on parameters. • Keep things fully customizable. • Decouple from framework. 36
  37. Performance Make things faster. Much faster.

  38. Benchmark • Give Tideways or Blackfire a spin. • Make

    performance part of your test suite. • In dev/test mode, use a meta block. 38
  39. Example { "meta": { "perf": { "db_time": 0.00367, "total_time": 0.3120,

    "memory": 19661 } } } 39
  40. Performance tips • Don't use lazy loading. Example: $product->getPhotos(). •

    Craft your own joins and carefully select fields. • Avoid ORM built-in hydration for read operations. 40
  41. Performance tips • Use API keys even for public endpoints

    (DDoS mitigation). • Put stuff in Memcached/Redis (especially blocked keys). • HTTP server can check headers. 41
  42. Useful links • Standard API format
 http://jsonapi.org/format/ • Digest implementation


    http://php.net/manual/en/features.http-auth.php • Book "Build APIs You Won't Hate"
 https://leanpub.com/build-apis-you-wont-hate 42
  43. Anna Filina • Development. • Fix bugs & performance issues.

    • Workshops on testing, frameworks & APIs. • Advisor on testing strategy, legacy code. 43
  44. @afilina afilina.com joind.in