owned this note
owned this note
Published
Linked with GitHub
# Build APIs You Won't Hate
[TOC]
## Useful Database Seeding
* What is `Seeding`?
* After created database, we need to test the database schema, `seeding` is the process that doing this. We will dump the dummy data into database and check for the result.
* This data chould be:
* test users
* content entries with a bunch of comments
* fake locations available for check-in
* fake notifications to display in an iPhone app
* credit-card payments at various stages of processing - with some complete, some half done and some super-fraudulent (not just fake data, the "real" fake one) looking ones.
* Benefit from Seeding.
* The process of creating seeding scripts means you can avoid wasting time creating them manually over and over again. (p.2)
:::danger
:warning: DON'T USE **PRODUCTION** DATA when SEEDING!
:::
:::info
PHP library [Faker](https://github.com/fzaniotto/Faker) Can help you fake the seeding data.
:::
### Building Seeders
* Bunch of the morden PHP framework provide the Database Seeding method (via CLI or others)
* Example code:
```PHP
$faker = Faker\Factory::create();
for ($i = 0; $i < Config::get('seeding.users'); $i++) {
$user = User::create([
'name' => $faker->name,
'email' => $faker->email,
'active' => $i === 0 ? true : $faker->boolean,
'gender' => $faker->randomElement(['male', 'female', 'other']),
'timezone' => $faker->numberBetween(-10, 10),
'birthday' => $faker->dateTimeBetween('-40 years', '-18 years'),
'location' => $faker->boolean ? "{$faker->city}, {$faker->state}" : null,
'had_feedback_email' => $faker->boolean,
'sync_name_bio' => $faker->boolean,
'bio' => $faker->sentence(100),
]);
}
```
:::warning
Store Time zones, not offsets (bad example code)
:::
### When to run?
* Both manually and automatically, depending on what is going on.
* e.g.
* Add a new endpoint with new data, run the migrations and run the seed.
* When deploy new builds of the API, automatically run by Jenkins or other CI.
## Planning and Creating Endpoints
### Endpoint Theory
#### GET Resources
**Embedding data**:
* `GET /resources`
* `GET /resources/X`
* `GET /resources/X,Y,Z`
Hard to pick between subresource URL or embedded data.
**Subresources**:
* `GET /places/X/checkins`
* `GET /users/X/checkins`
* `GET /usrs/X/checkins/Y`
:::danger
:warning: Auto-Increment is the Devil
:::
#### DELETE Resources
#### PUT Resources
* `POST /me/settings` - May setting specific field
* `PUT /me/settings` - Send ALL the settings
### Use Plural endpoint, don't mixed with singular
> *Consistency* is key
Mixing Plural & Singular endpoints could cause problem for inconsistency:
* `/opportunity/1`
* `/opportunities`
If, using plural for all endpoints:
* `/places`
* `/places/45`
* `/places/45,28`
And also for consistently named subresources:
* `/places/45/checkins`
* `/places/45/checkins/91`
* `/checkins/91`
**Consistency is key**.
### Verb or Noun (Hint: Noun)
Use **Noun**, left the verb to HTTP methods (that is RESTful)
* `GET /users/louie/messages`
* `PATCH /users/louie/messages/xGks83F`
* `DELETE /users/louie/messages/xGks83F`
POST for multiple data
```bash
POST /messages HTTP/1.1
Host: example.org
Content-Type: application/json
[
{
"user": {"id": 10},
"message": "Hello World"
},
{
"user": {"username": "louielu"},
"message": "Foo Bar"
}
]
```
## Planning Endpoints
If you need to list events, venues, users, and categories, **One controller for each type of resource**:
* CategoriesController
* EventsController
* UsersController
* VenuesController
> Every thing should be a resource, and each resource needs a controller
### Routes
Avoid magic routing conventions, best to write them manually.
### Methods
Fill the routes functions.
## Input and Output Theory
RESTful API input is only a HTTP request
### Requests
GET
```bash
GET /places?lat=40.759211&lon=-73.984638 HTTP/1.1
Host: api.example.org
```
POST
```bash
POST /moments/1/gift HTTP/1.1
Host: api.example.org
Authorization: Bearer vr5HmMkzlxKE70W1y4MibiJUusZwZC25NOVBEx3BD1
Content-Type: application/json
{ "user_id" : 2 }
```
### Response
```bash
HTTP/1.1 200 OK
Server: nginx
Content-Type: application/json
Connection: close
X-Powered-By: PHP/5.5.5-1+debphp.org~quantal+2
Cache-Control: no-cache, private
Date: Fri, 22 Nov 2013 16:37:57 GMT
Transfer-Encoding: Identity
{
"id":"1690",
"is_gift":true,
"user":{
"id":1,
"name":"Theron Weissnat",
"bio":"Occaecati excepturi magni odio distinctio dolores.",
"gender":"female",
"picture_url":"https:\/\/cdn.example.org/foo.png",
"timezone":-1,
"birthday":"1989-09-17 16:27:36",
"status":"available",
"created_at":"2013-11-22 16:37:57",
"redeem_by":"2013-12-22 16:37:57"
`}
}
```
:::warning
:warning: X-Powered-By in header should clouse by changing `expose_php` from `On` to `Off` in `php.ini`
:::
### Supporting Format
#### No Form Data
* PHP using `application/x-www-form-urlencoded`, which is not often seen on other language.
* PHP `$_GET` and `$_POST` is not 1:1 relationship, for example, when sending POST with query string, will let `$_GET` get nothing.
* And we can ignore `$_POST`, since everything is string in `application/x-www-form-urlencoded`
* GOOD
```bash=
POST /checkins HTTP/1.1
Host: api.example.org
Authorization: Bearer vr5HmMkzlxKE70W1y4MibiJUusZwZC25NOVBEx3BD1
Content-Type: application/json
{
"checkin": {
"place_id" : 1,
"message": "This is a bunch of text.",
"with_friends": [1, 2, 3, 4, 5]
}
}
```
* Don't, use x-www-form-urlencoded
```bash
POST /checkins HTTP/1.1
Host: api.example.org
Authorization: Bearer vr5HmMkzlxKE70W1y4MibiJUusZwZC25NOVBEx3BD1
Content-Type: application/x-www-form-urlencoded
checkin[place_id]=1&checkin[message]=This is a bunch of text&checkin[with_friends][]=\
1&checkin[with_friends][]=2&checkin[with_friends][]=3&checkin[with_friends][]=4&check\
in[with_friends][]=5
```
* Don't mix...
```bash
POST /checkins HTTP/1.1
Host: api.example.org
Authorization: Bearer vr5HmMkzlxKE70W1y4MibiJUusZwZC25NOVBEx3BD1
Content-Type: application/x-www-form-urlencoded
json="{
\"checkin\": {
\"place_id\" : 1,
\"message\": \"This is a bunch of text.\",
\"with_friends\": [1, 2, 3, 4, 5]
}
}"
```
#### JSON and XML
* Morden API will support JSON, yep.
* Bye, XML.
### Content structure
#### JSON-API
```bash
{
"posts": [{
"id": "1",
"title": "Rails is Omakase"
}]
}
```
* From: https://jsonapi.org/format/
* Pros:
* • Consistent response - It always has the same structure
* Cons:
* Some RESTful/Data utilities freak about having single responses in an array
* Potentially confusing to humans
#### Twitter-style
Ask for one user get one user
```
{
"name": Hulk Hogan",
"id": "100002"
}
```
Ask for a collections, get a collections of things
```
[
{
"name": "foo",
"id": "001"
},
{
"name": "bar",
"id": "002"
}
]
```
* Pros:
* Minimalistic response
* Almose every framework can comprehend it
* Cons
* No space for paginaion or other metadata
#### Facebook style
```
{
"name": "foo",
"id": "100"
}
```
```
{
"data": [
{
"name": "foo",
"id": "1000"
},
{
"name": "bar",
"id": "1001"
}
]
}
```
* Pros:
* Space for paginationand other metadata in collection
* Simplistic response even with the extra namespace
* Cons:
* Single items still can only have metadata by embedding int in the item resource.
#### Much Namespace, Nice output
```javascript
{
"data": {
"name": "Foo",
"id": "1000"
}
}
```
```bash
{
"data": [
{
"name": "foo",
"id": "1001"
},
{
"name": "bar",
"id": "1002"
}
]
}
```
All put in namespace, prevent polluted when using single data.
## Status Codes, Errors and Messages
* Do with error codes, to unify the result.
* Twitter: https://dev.twitter.com/overview/api/response-codes
### Error or Errors
* Errors,
```
{
"errors": [
{
"type": "OAuthException",
"code": "ERR-014234",
"message": "Just a dead dead exception",
"href": "https://github.example/api/users"
}
]
}
```
### Standards for Error Responses
#### JSON-API
* http://jsonapi.org/format/#errors
An error object MAY have the following members:
* "id" - A unique identifier for this particular occurrence of the problem.
* "herf" - A URI that MAY yield further details about this particular occurrence of the problem.
* "status"
* "code"
* "title"
* "detail"
* "links" - Associated resources,
* "path"
## Endpoint Testing
* Simplistic approach: BDD (Behaviour Driven Development)
* Using Cucumber: http://cukaes.info/
* Using Behat
### Setup
install Behat globally with Composer
```bash
composer global require "behat/behat ~2.5"
```
### Initialise
```bash
$ cd ~/app
$ mkdir -p tests/behat && cd tests/behat
$ behat --init
+d features - place your *.feature files here
+d features/bootstrap - place bootstrap scripts and static files here
+f features/bootstrap/FeatureContext.php - place your feature related code here
```
:::warning
SKIP
:::
## Outputting Data
:::warning
SKIP
:::
## Data Relationship
* Subresources
* Foreign Key Arrays
* Compound Documents(aka Sideloading)
* Embedded Documents (aka Nesting)
## Debugging
* Commandline
* cURL
* Browser Debugging
* RailsPanel
* Clockwork
* Chrome Logger
* var_dump
* Network Debugging
* pass
## Authentication
:::warning
SKIP
:::
## Pagination
:::warning
SKIP
:::
## HATEOAS
Hypermedia as the Engine of Application State
* Content negotiation
* Hypermedia controls
### Content negotiation
The output format of the resources
* Using HTTP header, instead of URI
Output JSON:
```bash
GET /places HTTP/1.1
Host: localhost:8000
Accpet: application/json
```
Output YAML:
```bash
GET /places HTTP/1.1
Host: localhost:8000
Accpet: application/x-yaml
```
PHP Code
```PHP
$mimeParts = (array) explode(';', Input::server('HTTP_ACCEPT'));
$mimeType = strtolower($mimeParts[0]);
switch ($mimeType) {
case 'application/json':
$contentType = 'application/json';
$content = json_encode($array);
break;
case 'application/x-yaml':
$contentType = 'application/x-yaml';
$dumper = new YamlDumper();
$content = $dumper->dump($array, 2);
break;
default:
$contentType = 'application/json';
$content = json_encode([
'error' => [
'code' => static::CODE_INVALID_MIME_TYPE,
'http_code' => 406,
'message' => spritnf('Content of type %s is not supported.', $mimeType)
]
]);
}
$response = Response::make($content, $this->statusCode, $headers);
$response->header('Content-Type', $contentType);
```
### Hypermedia Controls
* With URI in links
* "Just add links mate"
## API Versioning
* Nothing good, and consider not RESTful
* don't to bother on this problem
## File Uploads & Downloads