Slide 1

Slide 1 text

foolab.ca | @foolabca API Tips From the Frontline CakeFest, Amsterdam - May 29, 2016

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

Anna Filina • Developer • Problem solver • Teacher • Advisor • FooLab + ConFoo 3

Slide 4

Slide 4 text

Endpoints What URL to call when

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Response example: list { "data": [ {"name": "Skyrim"}, {"name": "Civilization V"} ], "meta": { "pages": 50, "count": 100 } } 7

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

Request/Response How to format stuff

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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 (with joins) by IDs and apply sorting. 13

Slide 14

Slide 14 text

LIMIT SELECT * FROM game LEFT JOIN rating ON rating.game_id = game.id WHERE game.price < 20 LIMIT 2; 14 id name price game_id score 1 Skyrim 19.99 1 9.1 1 Skyrim 19.99 1 8.9 2 Diablo 3 17.89 2 8.5

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

Guzzle 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); 17

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

Testing Simpler than you think

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

OAuth2 - conceptual diagram 28 User Client app
 (php/js/mobile) API Request Forward Validate Create token Store token Login form Login token user/pass

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

Implementation tips Is this a good way to code it?

Slide 35

Slide 35 text

Libraries • I use MVC frameworks & ORMs. • Aside from frameworks, I prefer small tools that don't do too much magic. 35

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Performance Make things faster. Much faster.

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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 42

Slide 43

Slide 43 text

Anna Filina • Development. • Fix bugs & performance issues. • Workshops on testing, frameworks & APIs. • Advisor on testing strategy, legacy code. 43

Slide 44

Slide 44 text

@afilina afilina.com joind.in