API Tips from the Frontline

API Tips from the Frontline

B3b2139e4f2c0eca4efe2379fcebc1c5?s=128

Anna Filina

June 09, 2015
Tweet

Transcript

  1. foolab.ca | @foolabca API Tips From the Frontline BEPHPUG, Berlin

    - June 9, 2015
  2. Anna Filina • Developer • Problem solver • Teacher •

    Advisor • FooLab + ConFoo 2
  3. What you will learn • Don't repeat my mistakes •

    Overcome common obstacles • Build elegant & pragmatic APIs 3
  4. Endpoints What URL to call when

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

    /products?page[number]=2&page[size]=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"} ], "page": { "number": 1, "size": 2, "pages": 50, "total": 100 } } 7
  8. Write • POST /products • PUT or PATCH /products/1 •

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

  10. Multiple formats • xml, json, 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": "Skyrim", "price": 19.99 } } 12
  13. Upload • Use tools, don't DIY (do it yourself). •

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

    url: '/api/upload', filters: [{extensions: 'jpg,png,jpeg'}] }); uploader.init(); 14
  15. Guzzle <? $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); 15
  16. Status codes • Don't confuse API & HTTP codes •

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

    { "code": 1001, "message": "Price must be greater than 0." } } 17
  18. Testing Simpler than you think

  19. HTTP • Use Guzzle or built-in framework tool • Generate

    HTTP request , compare output 19
  20. Guzzle test // tests/ApiProductTest.php public function testGetOneProduct() { $client =

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

    "id": "1", "name": "Skyrim", "price": 19.99 } }', $body); 21
  22. Testing tips • Create separate database for tests • Write

    tests before you code: • TDD • Contract between you and client dev 22
  23. Authentication Nooo, I hate that part! Someone else code it

    plz.
  24. 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 24
  25. OAuth2 • SSL required (can be risky) • Advanced features

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

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

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

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

  30. Progressive rewrite • Rewrite one component at a time •

    Start with whatever has fewer dependencies (or critical) • Delete dead code • Copy production data for dev environment 30
  31. Implementation tips Is this a good way to code it?

  32. Libraries • I use Symfony & Doctrine • Aside from

    frameworks, I prefer small tools that don't do too much magic 32
  33. Reuse & standardize • Goal: streamline endpoint creation • Base

    controller for common request processing • Base repository for querying with filters • Keep things fully customizable 33
  34. Performance Make things faster. Much faster.

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

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

    "memory": 19661 } } } 36
  37. 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. 37
  38. Performance tips • Use API keys even for public endpoints

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


    http://php.net/manual/en/features.http-auth.php • Symfony components
 http://symfony.com/doc/current/components/index.html • Book "Build APIs You Won't Hate"
 https://leanpub.com/build-apis-you-wont-hate 39
  40. Anna Filina • Development: PHP, JS, etc. • Fix problems:

    bugs, performance, etc. • Workshops: testing, Symfony, AngularJS, API, etc. • Advisor: testing strategy, legacy code, etc. 40
  41. @afilina afilina.com