Maxence POUTORD
GET /speaker/maxpou HTTP/1.1
{
"name": "Maxence POUTORD",
"skills": ["Symfony2", "API", "NodeJS", "Software quality"],
"hobbies": ["motorbike", "cinema (Danish)", "cooking"],
"job": {
"job": "web consultant",
"company": "Conserto"
}
"_links": {
"self": { "href": "maxpou.fr/slides/slides/about-rest/" },
"blog": { "href": "maxpou.fr" },
"Twitter": { "href": "twitter.com/_maxpou" },
"mail": { "href": "maxence.poutord@gmail.com" }
}
}
REST is NOT a protocol!
beer:
id: 42
shortName: Kwak
name: Pauwel Kwak
alcoholDegree: 8.4
color: amber
since: 1980
brewery: Bosteels Brewery
Kwak
Pauwel Kwak
8,4
Bosteels Brewery
Avoid:
/beers/42/
/beers/42.json
/beers//limit/26
/ugly_name/42
/UglyName/42
Instead, prefer:
/ugly-name/42
/beers?color=blond
/beers?offset=12&limit=5&exclude=2,3&sort=-alcohol
"Cool URIs don't change"
— W3C
Method | Safe? | Idempotent? |
GET | Yes | Yes |
HEAD | Yes | Yes |
POST | No | No |
PUT | No | Yes |
PATCH | No | No |
DELETE | No | Yes |
Safe = cacheable
Idempotent = result independent on the number of executions
1XX | Information |
2XX | Success |
3XX | Redirection |
4XX | Client Error |
5XX | Server Error |
GET /beers
Pauwel Kwak
8.4
Tripel Karmeliet
8.4
How can I interact with my Kwak?
If the engine of application state (and hence the API) is not being driven by hypertext, then it cannot be RESTful and cannot be a REST API. Period. [...]Roy T. FIELDING - REST APIs must be hypertext-driven
Please try to adhere to them or choose some other buzzword for your API.
Use links to describe HOW the service is used, and media types to describe WHAT is expect
Kwak
Pint
Tripel Karmeliet
Half
Pint
Kwak
Half
Tripel Karmeliet
Half
Pint
Before:
GET /fr/breweries/51.json
GET /en/breweries/51.xml
...
After:
GET /breweries/51
Accept: application/json, application/xml;q=0.9, text/html;q=0.8,
text/*;q=0.7, */*;q=0.5
Accept-Language: fr, en;q=0.8, de;q=0.7
JSON is not a hypermedia format
{
"_links": {
"self": { "href": "/beers?offset=1&limit=10" },
"next": { "href": "/beers?offset=11&limit=10" },
"ea:find": {
"href": "/beers{?id}",
"templated": true
}
}
}
{
"@context": "http://schema.org",
"@type": "Brewery",
"name": "Bosteels Brewery",
"url": "http://bestbelgianspecialbeers.be",
"address": {
"@type": "PostalAddress",
"streetAddress": "Kerkstraat 96",
"addressLocality": "9255 Buggenhout",
"addressRegion": "Belgium"
}
}
POST /contact
# mail body
HTTP/1.1 200 OK
Content-Type: application/json; version=2
Location: /breweries/51
Deprecated call?
HTTP/1.1 406 NOT ACCEPTABLE
Content-Type: application/json
["application/json; version=1", "application/json; version=2", "application/xml; version=1"]
/beers:
/{beerId}:
get:
description: Retrieve a specific beer
responses:
200:
body:
application/json:
example: |
{
"data": {
"id": "42",
"title": "Kwak",
"description": null,
"alcohol": 8.4
"_link": {
"_self": "http:/api.app.com/beers/42"
}
},
"success": true,
"status": 200
}
In a nutshell:
/**
* Get all Breweries entities
*
* @QueryParam(name="offset", requirements="\d+", nullable=true,
* description="Offset from which to start listing breweries.")
* @QueryParam(name="limit", requirements="\d+", nullable=true,
* description="How many breweries to return.")
*/
public function getBreweriesAction(ParamFetcher $paramFetcher)
{
$offset = $paramFetcher->get('offset');
$limit = $paramFetcher->get('limit');
$em = $this->getDoctrine()->getManager();
$breweries = $em ->getRepository('MaxpouBeerBundle:Brewery')
->findBy([], ['name' => 'ASC'], $limit, $offset);
return $breweries;
}
use Hateoas\Configuration\Annotation as Hateoas;
/**
* BreweryDTO
* No business logic here
* @Hateoas\Relation("self", href = @Hateoas\Route("api_get_brewerie", parameters = {"id" = "expr(object.getId())"}))
*/
class BreweryDTO
{
private $id;
private $name;
/** @return UUID object id **/
public function getId()
{
return $this->id;
}
}
GET breweries/6b9f3cfa-cc2c-11e5-8adf-2c4138ad20fa
{
"id": "6b9f3cfa-cc2c-11e5-8adf-2c4138ad20fa",
"name": "Bosteels brewery",
"_links": {
"self": {
"href": "/beerApp/web/app_dev.php/api/breweries/6b9f3cfa-cc2c-11e5-8adf-2c4138ad20fa"
}
}
}
<?php
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
/**
* Get all Breweries entities
*
* @ApiDoc(
* statusCodes={
* 200="Returned when successful"
* })
* @QueryParam(name="offset", requirements="\d+", nullable=true,
* description="Offset from which to start listing breweries.")
* @QueryParam(name="limit", requirements="\d+", nullable=true,
* description="How many breweries to return.")
*/
public function getBreweriesAction(ParamFetcher $paramFetcher)
{
//...
}
Bundles:
Examples:
"REST isn’t what you think it is, and that’s OK"
By Mark Masse