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

April 02, 2015
Tweet

More Decks by Anna Filina

Other Decks in Programming

Transcript

  1. foolab.ca | @foolabca
    API Tips From the
    Frontline
    PHP Quebec, Montreal - April 2, 2015

    View Slide

  2. Anna Filina
    • Developer
    • Problem solver
    • Coach
    • Advisor
    2

    View Slide

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

    View Slide

  4. Endpoints
    What URL to call when.

    View Slide

  5. Lists
    • /products
    • /products?category=games,movies&lang=fr
    • /products?include=platforms.name&sort=-date
    • /products?page[number]=2&page[size]=100
    5

    View Slide

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

    View Slide

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

    View Slide

  8. Request/Response
    How to format stuff.

    View Slide

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

    View Slide

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

    View Slide

  11. Request example
    POST /products HTTP/1.1
    Host: api.example.org
    Content-Type: application/json; charset=UTF-8
    {
    "data": {
    "name": "Skyrim",
    "price": 19.99
    }
    }
    11

    View Slide

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

    View Slide

  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 by IDs and apply sorting.
    13

    View Slide

  14. 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.
    • Look under $_FILES.
    14

    View Slide

  15. Plupload
    var uploader = new plupload.Uploader({
    runtimes: 'html5,html4',
    max_file_size: '5mb',
    url: '/api/upload',
    filters: [{extensions: 'jpg,png,jpeg'}]
    });
    uploader.init();
    15

    View Slide

  16. Guzzle

    $url = 'http://example.org/profiles/1/edit';
    $request = $client->createRequest('POST', $url);
    $postBody = $request->getBody();
    $postBody->setField('data', ['first_name':'Anna']);
    $file = new PostFile('i.jpg', fopen('/path', 'r'));
    $postBody->addFile($file);
    $response = $client->send($request);
    16

    View Slide

  17. Status codes
    • Don't confuse API & HTTP codes.
    • 2xx success
    • 3xx redirect
    • 4xx client error
    • 5xx server error
    • Send API-specific code in body.
    17

    View Slide

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

    View Slide

  19. Testing
    Simpler than you think.

    View Slide

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

    View Slide

  21. Guzzle test
    // tests/ApiProductTest.php
    public function testGetOneProduct()
    {
    $client = new Client();
    $response = $client->get('http://api.example.org/v1/products/1', [
    'exceptions' => false,
    'headers' => ['Accept' => 'application/json']
    ]);
    $this->assertEquals(200, $response->getStatusCode());
    $body = $response->getBody()->getContents();
    $this->assertJsonStringEqualsJsonString('{
    "data": {
    "id": "1",
    "name": "Skyrim",
    "price": 19.99
    }
    }', $body);
    }
    21

    View Slide

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

    View Slide

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

    View Slide

  24. Multiple methods
    • You can have multiple auth methods for one API.
    • Session can be one of them if same app.
    24

    View Slide

  25. OAuth2
    • SSL required.
    • Advanced features like access scope.
    • More security: use authorization server.
    25

    View Slide

  26. OAuth2 - Step 1 (simplified approach)
    26
    Login
    User Client app

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

    View Slide

  27. OAuth2 - Step 2
    27
    Fetch
    Display
    page
    Done
    token
    data
    page
    Access
    page
    Request
    data
    Validate
    token
    User Client app

    (php/js/mobile)
    API
    Authorization: Bearer zaVOP1lmQja4Lz-^RLwvzapvjd

    View Slide

  28. OAuth2 - Auth server
    28
    User Client app

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

    View Slide

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

    View Slide

  30. Micro-services
    30
    Products Orders Inventory
    /categories
    /products
    /reviews
    /customers
    /orders /warehouses
    /sales-reps
    get info
    update

    stock
    get info

    View Slide

  31. Progressive rewrite
    • Rewrite one component at a time.
    • Start with whatever has fewer dependencies (or critical).
    • Will have duplicate functionality for a time.
    ○ Example: orders will still fetch products the old way.
    • Copy production data for dev environment.
    31

    View Slide

  32. Services vs libraries
    • Services: GET /products?id=1,5
    • Libraries:
    • $db->getList( ["id" => "1,5"] );
    • Can fetch without pagination.
    • Can hydrate to models.
    • Bypass HTTP.
    32

    View Slide

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

    View Slide

  34. Libraries
    • I use Symfony & Doctrine.
    • Aside from frameworks, I prefer small tools that don't do
    too much magic.
    • OptionsResolver component to validate parameters.
    • Validator to validate entities (models).
    34

    View Slide

  35. OptionsResolver
    $resolver = new OptionsResolver();
    $resolver->setDefaults([
    'sort' => null,
    'lang' => 'en',
    ]);
    $resolver->setAllowedTypes('lang','string');
    $resolver->setAllowedValues('lang',['en', 'fr']);
    $resolver->resolve($options);
    35

    View Slide

  36. Validator
    $validator = Validation::createValidator();
    $constraint = new Assert\Collection([
    'name' => new Assert\Length(['min'=>2]),
    'photos' => new Assert\Collection([
    'file' => new Assert\Image(),
    ]),
    ]);
    $violations = $validator->validateValue($input,
    $constraint);
    36

    View Slide

  37. Validator
    • Can read constraints from model annotations.

    /** @Assert\ Length(min=5, minMessage="Too short") **/
    • List of violations for you to forward to client.
    • Can validate files, emails, IPs, credit cards, dates, etc.
    37

    View Slide

  38. Reuse & standardize
    • Goal: streamline endpoint creation.
    • Base controller for common request processing.
    • Base repository for querying with filters.
    • Keep things fully customizable.
    ◦ Not too much magic.
    ◦ I avoid out-of-the-box solutions.
    38

    View Slide

  39. Performance
    Make things faster. Much faster.

    View Slide

  40. Benchmark
    • Make performance part of your test suite.
    • Give Blackfire a spin.
    • In dev/test mode, use a meta block (or add to headers).
    40

    View Slide

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

    View Slide

  42. Perfomance 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.
    • Use API keys even for public endpoints (DDoS
    mitigation).
    • Put stuff in Memcached/Redis (especially blocked keys).
    42

    View Slide

  43. Useful links
    • Standard API format

    http://jsonapi.org/format/
    • Blackfire

    https://blackfire.io
    • OAuth2

    http://oauth2.thephpleague.com/
    • OAuth2 with mobile

    http://www.slideshare.net/briandavidcampbell/...
    • 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
    43

    View Slide

  44. Anna Filina
    • @afilina
    • afilina.com
    • Development: PHP, JS, etc.
    • Fix problems: bugs, performance, etc.
    • Workshops: testing, Symfony, AngularJS, API, etc.
    • Advisor: testing strategy, legacy code, etc.
    44

    View Slide

  45. @afilina afilina.com

    View Slide