The key to victory is discipline,
and that means a well made bed.
Not sure if trolling or just “technically correct”
This chronological wang-dang-doodle
could destroy the very matrix of reality.
/api
) and one METHOD (POST
)
$_POST
) you get back some data
GET
POST
request to /api
with $_POST['action']
set to getLecturers
and get back a list of lecturers as the actual content
{
"lecturers":
[
{
"id": 1,
"name": "Davy De Winne"
},
{
"id": 2,
"name": "Bramus Van Damme"
}
]
}
/product
/products
/product/1234
/products/1234
/photos/product/1234
/products/1234/photos
/photos/product/1234/5678
/products/1234/photos/5678
/things
/animals
/dogs
/beagles
/search?course=WMD
/convert?from=EUR&to=USD&amount=100
/
/movies
/movies/{id}
/movies/{id}/photos
/actors
/actors/{id}
/actors/{id}/movies
/about
/contact
POST
request to /api/lecturers
and get back a list of lecturers as the actual content
{
"lecturers":
[
{
"id": 1,
"name": "Davy De Winne"
},
{
"id": 2,
"name": "Bramus Van Damme"
}
]
}
api.twitter.com/v1/…
is wrong)
?filter=foo&sort=asc
?fields=firstname,lastname,age
limit
and offset
URL params
?limit=10&offset=<max_id>
offset
, opt for id-constraint based on sorting method; this avoids doubles when paginating over a frequently updated result set.POST
, but use all HTTP verbs
POST
, GET
, PUT
, PATCH
, DELETE
, …404
, 500
, …POST
= Create = INSERT
GET
= Read = SELECT
PUT
= Update = UPDATE
PATCH
= Update = UPDATE
DELETE
= Delete = DELETE
POST /products
(data via $_POST
)GET /products
or GET /products/1234
PUT /products/1234
(data via “$_PUT
”)DELETE /products/1234
PUT
= replace a resource entirely
PATCH
= partially replace a resource
ThePATCH
method applies a delta (diff) rather than replacing the entire resource. The request entity toPATCH
is of a different content-type that the resource that is being modified. Instead of being an entire resource representation, it is a resource that describes changes to apply on a resource.
Source: Please don't patch like an idiot
400 — Bad Request
(e.g. Request Body is malformed / not parseable)401 — Not Authorized
(e.g. No API Key given)405 — Method Not Allowed
(e.g. wrong verb for resource)415 — Unsupported Media Type
422 — Unprocessable Entity
(Validation Errors)429 — Too Many Requests
(Rate Limiting)500 — Internal Server Error
(Developer screwed up ;))GET
(SQL SELECT)
200 — OK
(requested data sent via response body)404 — Not Found
(resource not found, not “API not found”)POST
(SQL INSERT)
201 — Created
(=OK)400 — Bad Request
(Missing Parameters)PUT
(SQL UPDATE)
200 — OK
(requested data sent via response body)400 — Bad Request
(Missing Parameters)404 — Not Found
DELETE
(SQL DELETE)
204 — No Content
(=OK — Note: no response body sent!)404 — Not Found
$.ajax().error()
in jQuery!GET
request to /api/lecturers
and get back an HTTP 200
status code and a list of lecturers as the content
{
"lecturers":
[
{
"id": 1,
"name": "Davy De Winne"
},
{
"id": 2,
"name": "Bramus Van Damme"
}
]
}
POST
request to /api/lecturers
with some data in $_POST
to insert a new lecturer
201 — Created
if all OK400 — Bad Request
if parameters are missingPUT
request to /api/lecturers/2
with some data in $_PUT
to update a lecturer
200 — OK
if all OK400 — Bad Request
if parameters are missing404 — Not Found
if the lecturer does not existThe simultaneous presentation of information and controls such that the information becomes the affordance through which the user obtains choices and selects actions⚑
application/vnd.ikdoeict.whatever
accept
header which
Accept: application/collection+json
Accept: application/xml; q=0.8, application/json
Accept: application/vnd.ikdoeict.whatever-v2+json
Accept: application/collection+json;v=2
application/json
(Sorry Roy, gotta pave the cowpaths)
status
and content
objectlinks
array holding the links
rel
and href
property
rel
values are based on RFC5988
self
, up
, index
, next
, prev
, ...photos
)GET
request to /lecturers
and get back a custom JSON response with HTTP status code 200
containing a list of lecturers (with resource links) as the response content
{
"status":
{
"code": 200,
"text": "OK"
},
"content":
{
"size": 2,
"lecturers":
[
{
"id": 1,
"name": "Davy De Winne",
"links": [
{
"rel": "self",
"href": "/users/1"
}
]
},
{
"id": 2,
"name": "Bramus Van Damme",
"links": [
{
"rel": "self",
"href": "/users/2"
}
]
}
]
}
}
application/json
— It cuts the mustard
Hey, look! I found a robot fossil!
GET api.twitter.com/1/statuses/show/id.format
show
included in URLid
not a child of statuses
.format
at the end)GET api.twitter.com/statuses/{id}
POST api.twitter.com/1/statuses/update.format
update
included in URL.format
at the end)POST api.twitter.com/statuses
Would it cheer you up if I punch Fry in the groin?
Cause I'll do it, regardless.
GET
request to /movies
, show a list of moviesGET
request to /movies/{id}
, show the one specific moviePOST
request to /movies
, insert it$router = new Router();
$router->get('/', function() { … });
$router->get('/movies', function() { … });
$router->get('/movies/\d+', function() { … });
$router->post('/movies', function() { … });
$router->run();
Response
class
$status
and $content
datamembers + mutators200
\Symfony\Component\HttpFoundation\Response
!⚑
getContent()
to transform the content to the format we need
Content-Type: application/json
header$_PUT
, you'll need to fake it<?php
//…
$router->put('/movies/\d+', function() {
// Fake $_PUT
$_PUT = array();
parse_str(file_get_contents('php://input'), $_PUT);
// …
});
//…
To the flying machine!
success
and error
functions
$.ajax({
url : 'http://api.myapp.tld/',
type: 'get|post|put|delete',
dataType : 'json',
data: 'foo=bar'
})
.success(function(data, textStatus, jqXHR) {
if (data) {
// All OK and data returned (200, 201)
} else {
// All OK but no data returned (e.g. 204 after a DELETE)
}
})
.error(function(jqXHR, textStatus, errorThrown) {
var data = jqXHR.responseText ? JSON.parse(jqXHR.responseText) : {};
// Got a 400 (params), 401, 404, 405 (method), 500, or 501 (not impl)
});
Who are those horrible orange creatures over there? — Grunka-Lunkas. — Tell them I hate them!
$router->before('GET|POST|PUT|DELETE', '.*', function() use ($response) {
$headers = apache_request_headers();
if(!isset($headers['X-Api-Key']) || !ApiDB::isValidApiKey($headers['X-Api-Key'])) {
$response->setStatus(401);
$response->setContent('Missing or invalid API Key.');
$response->finish();
}
});
X
and all words must be ucfirst'd and separated with -
apache_request_headers()
not always available (IIS)!
if (!function_exists('apache_request_headers')) {
eval('
function apache_request_headers() {
foreach($_SERVER as $key=>$value) {
if (substr($key,0,5)=="HTTP_") {
$key=str_replace(" ","-",ucwords(strtolower(str_replace("_"," ",substr($key,5)))));
$out[$key]=$value;
}
}
return $out;
}
');
}
client_id
) for identification), asking if consumer app may access user data at a certain scope
(read/write)redirect_uri
with an authorization_code
authorization_code
, along with its API-Key (client_id
) and secret key (client_secret
)access_token
and refresh_token
access_token
must passed with all calls made and is valid for a limited timeaccess_token
has expired, the consumer app needs to do a call to the OAuth Prodider with the refresh_token
to get a new pair access_token
& refresh_token