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

April 02, 2015
Tweet

More Decks by Anna Filina

Other Decks in Programming

Transcript

  1. What you will learn • Don't repeat my mistakes. •

    Overcome common obstacles. • Build elegant APIs. 3
  2. Write • POST /products • PUT /products/1 • DELETE /products/1

    • Version your APIs: api.example.org/v1/products 7
  3. Multiple formats • xml, json, etc. • Can use 1

    endpoint with Accept header. • Can send version, pagination & language info using headers too. 9
  4. Response example: list { "data": [ {"name": "Skyrim"}, {"name": "Civilization

    V"}, ], "page": { "number": 1, "size": 2, "pages": 50, "total": 100, } } 12
  5. 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
  6. 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
  7. Plupload var uploader = new plupload.Uploader({ runtimes: 'html5,html4', max_file_size: '5mb',

    url: '/api/upload', filters: [{extensions: 'jpg,png,jpeg'}] }); uploader.init(); 15
  8. 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
  9. 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
  10. Example of error Status: 400 Content-Type: application/json; charset=UTF-8 { "error":

    { "code": 1001, "message": "Price must be greater than 0." } } 18
  11. 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
  12. Testing tips • Create separate database for tests. • Write

    tests before you code: • TDD • Contract between you and frontend dev. 22
  13. Multiple methods • You can have multiple auth methods for

    one API. • Session can be one of them if same app. 24
  14. OAuth2 • SSL required. • Advanced features like access scope.

    • More security: use authorization server. 25
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. OptionsResolver $resolver = new OptionsResolver(); $resolver->setDefaults([ 'sort' => null, 'lang'

    => 'en', ]); $resolver->setAllowedTypes('lang','string'); $resolver->setAllowedValues('lang',['en', 'fr']); $resolver->resolve($options); 35
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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