Upgrade to Pro — share decks privately, control downloads, hide ads and more …

API Tips from the Frontline

API Tips from the Frontline

Anna Filina

June 09, 2015
Tweet

More Decks by Anna Filina

Other Decks in Programming

Transcript

  1. foolab.ca | @foolabca
    API Tips From the
    Frontline
    BEPHPUG, Berlin - June 9, 2015

    View full-size slide

  2. Anna Filina
    • Developer
    • Problem solver
    • Teacher
    • Advisor
    • FooLab + ConFoo
    2

    View full-size slide

  3. What you will learn
    • Don't repeat my mistakes
    • Overcome common obstacles
    • Build elegant & pragmatic APIs
    3

    View full-size slide

  4. Endpoints
    What URL to call when

    View full-size slide

  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

    View full-size slide

  6. Details
    • /products/1
    • /products/1?include=photos,reviews
    6

    View full-size slide

  7. Response example: list
    {
    "data": [
    {"name": "Skyrim"},
    {"name": "Civilization V"}
    ],
    "page": {
    "number": 1,
    "size": 2,
    "pages": 50,
    "total": 100
    }
    }
    7

    View full-size slide

  8. Write
    • POST /products
    • PUT or PATCH /products/1
    • DELETE /products/1
    • Version your APIs: api.example.org/v1/products
    8

    View full-size slide

  9. Request/Response
    How to format stuff

    View full-size slide

  10. Multiple formats
    • xml, json, etc.
    • Can use 1 endpoint with Accept header
    • Can send version, pagination & language info using
    headers too
    10

    View full-size slide

  11. Request
    • Bad: Content-Type: application/x-www-form-urlencoded
    • Content-Type: application/json
    • Send content (POST) in body, not headers
    11

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  17. Example of error
    Status: 400
    Content-Type: application/json; charset=UTF-8
    {
    "error": {
    "code": 1001,
    "message": "Price must be greater than 0."
    }
    }
    17

    View full-size slide

  18. Testing
    Simpler than you think

    View full-size slide

  19. HTTP
    • Use Guzzle or built-in framework tool
    • Generate HTTP request , compare output
    19

    View full-size slide

  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

    View full-size slide

  21. Guzzle test
    // ...
    $body = $response->getBody()->getContents();
    $this->assertJsonStringEqualsJsonString('{
    "data": {
    "id": "1",
    "name": "Skyrim",
    "price": 19.99
    }
    }', $body);
    21

    View full-size slide

  22. Testing tips
    • Create separate database for tests
    • Write tests before you code:
    • TDD
    • Contract between you and client dev
    22

    View full-size slide

  23. Authentication
    Nooo, I hate that part! Someone else code it plz.

    View full-size slide

  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

    View full-size slide

  25. OAuth2
    • SSL required (can be risky)
    • Advanced features like access scope
    • Can be overkill if you need basic features
    • Private credentials
    25

    View full-size slide

  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

    View full-size slide

  27. Digest
    • Its own encryption
    • Easy to implement
    • No replay (nonce)
    • Comes out-of-the-box with some frameworks
    27

    View full-size slide

  28. Digest - conceptual diagram
    28
    Request
    User/client app API
    Unauthorized
    Validate
    digest (nonce,pass)
    Request
    nonce

    View full-size slide

  29. Refactoring to API
    Don't rewrite all the legacy at once

    View full-size slide

  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

    View full-size slide

  31. Implementation tips
    Is this a good way to code it?

    View full-size slide

  32. Libraries
    • I use Symfony & Doctrine
    • Aside from frameworks, I prefer small tools that don't do
    too much magic
    32

    View full-size slide

  33. Reuse & standardize
    • Goal: streamline endpoint creation
    • Base controller for common request processing
    • Base repository for querying with filters
    • Keep things fully customizable
    33

    View full-size slide

  34. Performance
    Make things faster. Much faster.

    View full-size slide

  35. Benchmark
    • Give Tideways or Blackfire a spin
    • Make performance part of your test suite
    • In dev/test mode, use a meta block
    35

    View full-size slide

  36. Example
    {
    "meta": {
    "perf": {
    "db_time": 0.00367,
    "total_time": 0.3120,
    "memory": 19661
    }
    }
    }
    36

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  41. @afilina afilina.com

    View full-size slide