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

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 and input errors? How to handle write operations and file uploads? 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.

Anna Filina
PRO

May 31, 2017
Tweet

More Decks by Anna Filina

Other Decks in Programming

Transcript

  1. @afilina
    API Tips From the Frontline
    IPC, Berlin - May 31, 2017

    View Slide

  2. Objectives
    • Don't repeat my mistakes.
    • Overcome common obstacles.
    • Build elegant & pragmatic APIs.

    View Slide

  3. Anna Filina
    • Project rescue expert
    • Dev, trainer, speaker

    View Slide

  4. Endpoints
    What URL to call when

    View Slide

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

    View Slide

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

    View Slide

  7. Response Example: List
    {
    "data": [
    {"name": "Skyrim"},
    {"name": "Civilization V"}
    ],
    "meta": {
    "pages": 50,
    "count": 100
    }
    }

    View Slide

  8. Verbs
    • POST /products
    • PUT or PATCH /products/1
    • DELETE /products/1
    • Version your APIs:
    ◦ api.example.org/v1/products
    ◦ Headers also an option.

    View Slide

  9. Request/response
    How to format stuff

    View Slide

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

    View Slide

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

    View Slide

  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
    }
    }

    View Slide

  13. 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.

    View Slide

  14. Plupload
    var uploader = new plupload.Uploader({
    runtimes: 'html5,html4',
    url: '/upload'
    });
    uploader.init();

    View Slide

  15. Guzzle
    $url = 'http://api.dev/profiles/1/edit';
    $request = $client->createRequest('PATCH', $url);
    $file = new PostFile('me.jpg', fopen('/me.jpg', 'r'));
    $body = $request->getBody();
    $body->setField('data', ['first_name':'Anna']);
    $body->addFile($file);
    $response = $client->send($request);

    View 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.

    View Slide

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

    View Slide

  18. Testing
    Simpler than you think

    View Slide

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

    View Slide

  20. Guzzle Test
    // tests/ApiProductTest.php
    public function test_GetOneProduct_ReturnsSuccess()
    {
    $client = new Client();
    $response = $client->get('http://api.dev/products/1', [
    'exceptions' => false,
    'headers' => ['Accept' => 'application/json']
    ]);
    $this->assertEquals(200, $response->getStatusCode());
    // ...
    }

    View Slide

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

    View Slide

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

    View Slide

  23. Authentication
    Ugh, THAT part

    View 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.

    View Slide

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

    View Slide

  26. OAuth2 - Conceptual Diagram
    User Client app

    (php/js/mobile)
    API
    Request Forward
    Validate
    Create token
    Store token
    Login form
    Login
    token
    user/pass

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  30. Progressive Rewrite
    • Rewrite one component/module at a
    time.
    • Start with whatever has fewer
    dependencies (or most critical).
    • Delete dead code.
    • Copy production data for dev
    environment.

    View Slide

  31. Services vs Libraries
    • Services: GET /products?category=game
    • Libraries/classes:
    ◦ $table->getApiList( ["category" => "game"] );
    ◦ Bypass HTTP
    ◦ Can fetch without pagination.
    ◦ Can hydrate to models.

    View Slide

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

    View Slide

  33. Tools
    • I use MVC frameworks & ORMs.
    • Aside from frameworks, I prefer small
    libraries that don't do too much magic.

    View Slide

  34. Reuse & Standardize
    • Goal: streamline endpoint creation.
    • Automate request parameters
    extraction.
    • Automate query generation based on
    parameters.
    • Keep things fully customizable.
    • Decouple from framework.

    View Slide

  35. Performance
    Make things faster. Much faster.

    View Slide

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

    View Slide

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

    View Slide

  38. 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.

    View Slide

  39. Performance Tips
    • Use API keys even for public endpoints
    (DDoS mitigation).
    • Put stuff in Memcached/Redis
    (especially blocked keys).
    • HTTP server can check headers.

    View Slide

  40. 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

    View Slide

  41. @afilina afilina.com
    joind.in

    View Slide