@see jsonapi.org
Philipp Marien / @philippmarien | Paul Gütschow
JSON API is a specification for how a client should request that resources be fetched or modified, and how a server should respond to those requests.http://jsonapi.org/format/#introduction
2013-05-03 ⇛ Initial release
2015-05-29 ⇛ 1.0 final release
REST over HTTP
Content-Type: application/vnd.api+json
Documents
{
"links": { ... },
"data": [ ... ],
"included": [ ... ],
"errors": [ ... ],
"meta": { ... },
"jsonapi": {
"version": "1.0"
}
}
like Entities, Identified by URI's
{
"type": "talks",
"id": "c9069d5c-1205-4043-9b8a-cb833f7235b5",
"attributes": {
"title": "JSON API"
},
"relationships": { ... }, //optional
"links": { ... }, //optional
"meta": { ... } //optional
}
{
"type": "talks",
"id": "c9069d5c-1205-4043-9b8a-cb833f7235b5",
"meta": { ... } // optional
}
{
"data": { ... },
"included": [
{
"type": "persons",
"id": "69d447ed-a9a4-41f8-8904-bfc597a0e2b9",
"attributes": {
"firstName": "Philipp",
"lastName": "Marien"
},
"relationships": { ... }
}
]
}
"meta": {
"createdAt": "2017-06-06T09:30:00+02:00"
}
"links": {
"self": "http://example.com/talks/c9069d5c..."
}
"links": {
"authors": {
"href": "http://example.com/talks/c9069d5c.../authors",
"meta": {
"count": 1
}
}
}
"errors": [
{
"id": "e1e468ec-4762-47e3-ad5d-8651d39b6082",
"links": {
"about": "http/example.com/errors/e1e468ec-4762-47e3-ad5d-8651d39b6082"
},
"status": "400",
"code": "1",
"title": "Invalid Request",
"detail": "Filter \"title\" can not be applied on post request!",
"source": {
"pointer": "/data",
"parameter": "filter[title]"
},
"meta": {
"trace": "..."
}
}
]
"relationships": {
"slides": {
"links": {
"self": "http://example.com/talks/c9069d5c-1205-4043-9b8a-cb833f7235b5/relationships/slides",
"related": "http://example.com/talks/c9069d5c-1205-4043-9b8a-cb833f7235b5/slides"
},
"data": [
{
"type": "slides",
"id": "964179f2-cea8-49a6-ba07-58083111adb7",
"meta": {
"page": 1
}
}
],
"meta": {
"count": 1
}
},
"authors": {
"data": [
{
"type": "persons",
"id": "69d447ed-a9a4-41f8-8904-bfc597a0e2b9"
}
]
}
}
Fetch - Create -Patch - Delete
GET /{type}/{id}
GET /talks/c9069d5c-1205-4043-9b8a-cb833f7235b5
{
"data": {
"type": "talks",
"id": "c9069d5c-1205-4043-9b8a-cb833f7235b5",
"attributes": {
"title": "JSON API"
},
"relationships": { ... },
"links": { ... },
"meta": {
"createdAt": "2017-06-06T09:30:00+02:00"
}
}
}
GET /{type}
GET /talks
{
"data": [
{ ... },
{ ... }
]
}
GET /{type}/{id}/relationship/{relationship}
GET /talks/c9069d5c-1205-4043-9b8a-cb833f7235b5/relationship/authors
{
"data": [
{
"type": "persons",
"id": "69d447ed-a9a4-41f8-8904-bfc597a0e2b9"
}
]
}
GET /{type}/{id}/{relationship}
GET /talks/c9069d5c-1205-4043-9b8a-cb833f7235b5/authors
{
"data": [
{
"type": "persons",
"id": "69d447ed-a9a4-41f8-8904-bfc597a0e2b9",
"attributes": {
"firstName": "Philipp",
"lastName": "Marien"
}
}
]
}
POST /{type}
POST /talks
{
"data": {
"type": "talks",
// optional UUID, generated by server if not provided
"id": "f3c5cb5c-bd34-4727-ad39-1110b99cfcb9",
"attributes": {
"title": "JSON API PHP Library"
}
}
}
PATCH /{type}/{id}
PATCH /talks/c9069d5c-1205-4043-9b8a-cb833f7235b5
{
"data": {
"type": "talks",
"id": "c9069d5c-1205-4043-9b8a-cb833f7235b5",
"attributes": {
"title": "JSON API - Slides"
}
}
}
DELETE /{type}/{id}
GET /talks?include=authors
GET /talks?include=authors,slides
GET /talks/c9069d5c-1205-4043-9b8a-cb833f7235b5?include=slides
GET /talks?include=authors.talks
GET /talks?include=authors,authors.talks
GET /persons?fields[persons]=firstName
GET /persons?fields[persons]=firstName,lastName
GET /persons?include=talks&fields[persons]=firstName,lastName&fields[talks]=title
GET /talks?sort=title
GET /persons?sort=lastName,firstName
GET /talks?sort=-title
GET /talks?sort=authors.lastName
GET /talks?page[offset]=0&page[limit]=10
GET /talks?page[number]=1&page[size]=10
{
"links": {
"self": "http://example.com/talks?page[offset]=9&page[limit]=10",
"first": "http://example.com/talks",
"last": "http://example.com/talks?page[offset]=29&page[limit]=10",
"prev": "http://example.com/talks?page[offset]=0&page[limit]=10",
"next": "http://example.com/talks?page[offset]=19&page[limit]=10"
},
"data": { ... }
}
GET /talks?filter[title]=JSON
GET /talks?filter[author][firstName]=Philipp
composer require enm/json-api-common
composer require enm/json-api-server
<?php declare(strict_types=1);
require __DIR__.'/vendor/autoload.php';
$requestHandler = new \Enm\JsonApi\Server\RequestHandler\RequestHandlerRegistry();
$requestHandler->addRequestHandler('talks', new TalkRequestHandler());
$jsonApi = new \Enm\JsonApi\Server\JsonApiServer($requestHandler, '/api');
$request = new \GuzzleHttp\Psr7\Request(
$_SERVER['REQUEST_METHOD'],
$_SERVER['REQUEST_URI'],
getallheaders(),
file_get_contents('php://input')
);
\Http\Response\send($jsonApi->handleHttpRequest($request));
$this->jsonApi();
\Enm\JsonApi\Server\RequestHandler
use \Enm\JsonApi\Server\Pagination\PaginationTrait;
$this->setPaginationLinkGenerator(
new \Enm\JsonApi\Server\Pagination\OffsetPaginationLinkGenerator()
);
$this->paginate($document, $request, $resultCount);
composer require enm/json-api-server-bundle
enm_json_api_server:
debug: false
api_prefix: "/api"
logger: "logger"
psr7_factory: "your_psr7_factory_service"
http_foundation_factory: "your_http_foundation_factory_service"
pagination:
limit: 10
json_api:
resource: "@EnmJsonApiServerBundle/Resources/config/routing.xml"
AppBundle\RequestHandler\TalkRequestHandler:
tags:
- { name: 'json_api_server.request_handler', type: 'talks' }
AppBundle\RequestHandler\YourGenericRequestHandler:
tags:
- { name: 'json_api_server.request_handler' }
AppBundle\ResourceProvider\TalkProvider:
tags:
- { name: 'json_api_server.resource_provider', type: 'talks' }