{"id":13752835,"url":"https://github.com/nilportugues/laravel5-jsonapi","last_synced_at":"2025-04-13T04:59:55.678Z","repository":{"id":57027210,"uuid":"40797469","full_name":"nilportugues/laravel5-jsonapi","owner":"nilportugues","description":"Laravel 5 JSON API Transformer Package","archived":false,"fork":false,"pushed_at":"2017-09-27T10:58:14.000Z","size":562,"stargazers_count":309,"open_issues_count":41,"forks_count":70,"subscribers_count":19,"default_branch":"master","last_synced_at":"2025-04-13T04:59:47.027Z","etag":null,"topics":["api","json","json-api","jsonapi","laravel","laravel5","lumen","microservice","microservices","php","php7","transformer"],"latest_commit_sha":null,"homepage":"http://nilportugues.com","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nilportugues.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-08-16T03:25:54.000Z","updated_at":"2025-02-11T21:32:03.000Z","dependencies_parsed_at":"2022-08-23T16:20:37.726Z","dependency_job_id":null,"html_url":"https://github.com/nilportugues/laravel5-jsonapi","commit_stats":null,"previous_names":[],"tags_count":52,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nilportugues%2Flaravel5-jsonapi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nilportugues%2Flaravel5-jsonapi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nilportugues%2Flaravel5-jsonapi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nilportugues%2Flaravel5-jsonapi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nilportugues","download_url":"https://codeload.github.com/nilportugues/laravel5-jsonapi/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248665756,"owners_count":21142123,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","json","json-api","jsonapi","laravel","laravel5","lumen","microservice","microservices","php","php7","transformer"],"created_at":"2024-08-03T09:01:11.549Z","updated_at":"2025-04-13T04:59:55.629Z","avatar_url":"https://github.com/nilportugues.png","language":"PHP","funding_links":["https://paypal.me/nilportugues"],"categories":["transformer"],"sub_categories":[],"readme":"# Laravel 5 JSON API Server Package\n\n\n[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/nilportugues/laravel5-jsonapi-transformer/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/nilportugues/laravel5-jsonapi-transformer/?branch=master) [![SensioLabsInsight](https://insight.sensiolabs.com/projects/22db88f5-d061-4b32-bad1-4b806ac07318/mini.png)](https://insight.sensiolabs.com/projects/22db88f5-d061-4b32-bad1-4b806ac07318) \n[![Latest Stable Version](https://poser.pugx.org/nilportugues/laravel5-json-api/v/stable)](https://packagist.org/packages/nilportugues/laravel5-json-api) \n[![Total Downloads](https://poser.pugx.org/nilportugues/laravel5-json-api/downloads)](https://packagist.org/packages/nilportugues/laravel5-json-api) \n[![License](https://poser.pugx.org/nilportugues/laravel5-json-api/license)](https://packagist.org/packages/nilportugues/laravel5-json-api) \n[![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://paypal.me/nilportugues)\n\n*Compatible with Laravel 5.0, 5.1 \u0026 5.2*\n\n- Package provides a full implementation of the **[JSON API](https://github.com/json-api/json-api)** specification, and is **featured** on the official site!\n- A **JSON API Transformer** that will allow you to convert any mapped object into a valid JSON API resource.\n- Controller boilerplate to write a fully compiliant **JSON API Server** using your **exisiting Eloquent Models**.\n- Works for Laravel 5 and Lumen frameworks.\n\n---\n\n- [Installation](#installation)\n- [Configuration (Laravel 5 \u0026 Lumen)](#configuration-laravel-5--lumen)\n  - [Configuration for Laravel 5](#configuration-for-laravel-5)\n    - [Step 1: Add the Service Provider](#step-1-add-the-service-provider)\n    - [Step 2: Defining routes](#step-2-defining-routes)\n    - [Step 3: Definition](#step-3-definition)\n    - [Step 4: Usage](#step-4-usage)\n  - [Configuration for Lumen](#configuration-for-lumen)\n    - [Step 1: Add the Service Provider](#step-1-add-the-service-provider-1)\n    - [Step 2: Defining routes](#step-2-defining-routes-1)\n    - [Step 3: Definition](#step-3-definition-1)\n    - [Step 4: Usage](#step-4-usage-1)\n- [JsonApiController](#jsonapicontroller)\n- [Examples: Consuming the API](#examples-consuming-the-api)\n  - [GET](#get)\n  - [POST](#post)\n  - [PUT](#put)\n  - [PATCH](#patch)\n  - [DELETE](#delete)\n- [GET Query Params: include, fields, sort and page](#get-query-params-include-fields-sort-and-page)\n- [POST/PUT/PATCH with Relationships](#postputpatch-with-relationships)\n- [Custom Response Headers](#custom-response-headers)\n- [Common Errors and Solutions](#common-errors-and-solutions)\n\n## Installation\n\nUse [Composer](https://getcomposer.org) to install the package:\n\n```\ncomposer require nilportugues/laravel5-json-api\n```\n\nNow run the following artisan command: \n\n```\nphp artisan vendor:publish\n```\n\n## Configuration (Laravel 5 \u0026 Lumen)\n\n\nFor the sake of having a real life example, this configuration will guide you on how to set up **7 end-points** for two resources, `Employees` and `Orders`.\n\nBoth `Employees` and `Orders` resources will be **Eloquent** models, being related one with the other. \n\nFurthermore, `Employees`will be using an Eloquent feature, `appended fields` to demonstrate how it is possible to make the most of Eloquent and this package all together.\n\n### Configuration for Laravel 5\n\n#### Step 1: Add the Service Provider\n\nOpen up `config/app.php` and add the following line under `providers` array:\n\n```php\n'providers' =\u003e [\n    //...\n    NilPortugues\\Laravel5\\JsonApi\\Laravel5JsonApiServiceProvider::class,\n],\n```\n\n\n\n#### Step 2: Defining routes\n\nWe will be planning the resources ahead its implementation. All routes require to have a name.  \n\nThis is how our `app/Http/routes.php` will look:\n\n\n```php\n\u003c?php\nRoute::group(['namespace' =\u003e 'Api'], function() {\n    Route::resource('employees', 'EmployeesController');    \n    Route::get(\n        'employees/{employee_id}/orders', [\n        'as' =\u003e 'employees.orders',\n        'uses' =\u003e 'EmployeesController@getOrdersByEmployee'\n    ]);\n});\n//...\n```\n\n#### Step 3: Definition\n\n\nFirst, let's define the Models for `Employees` and `Orders` using Eloquent.\n\n\n**Employees (Eloquent Model)**\n```php\n\u003c?php namespace App\\Model\\Database;\n\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Foundation\\Validation\\ValidatesRequests;\n\nclass Employees extends Model\n{\n    public $timestamps = false;\n    protected $table = 'employees';    \n    protected $primaryKey = 'id';\n    protected $appends = ['full_name'];\n\n    /**\n     * @return \\Illuminate\\Database\\Eloquent\\Relations\\HasOne\n     */\n    public function latestOrders()\n    {\n        return $this-\u003ehasMany(Orders::class, 'employee_id')-\u003elimit(10);\n    }\n\n    /**\n     * @return string\n     */\n    public function getFullNameAttribute()\n    {\n        return $this-\u003efirst_name.' '.$this-\u003elast_name;\n    }\n}\n\n```\n\n**Employees SQL**\n\n```sql\nCREATE TABLE `employees` (\n  `id` int(11) NOT NULL AUTO_INCREMENT,\n  `company` varchar(50) DEFAULT NULL,\n  `last_name` varchar(50) DEFAULT NULL,\n  `first_name` varchar(50) DEFAULT NULL,\n  `email_address` varchar(50) DEFAULT NULL,\n  `job_title` varchar(50) DEFAULT NULL,\n  `business_phone` varchar(25) DEFAULT NULL,\n  `home_phone` varchar(25) DEFAULT NULL,\n  `mobile_phone` varchar(25) DEFAULT NULL,\n  `fax_number` varchar(25) DEFAULT NULL,\n  `address` longtext,\n  `city` varchar(50) DEFAULT NULL,\n  `state_province` varchar(50) DEFAULT NULL,\n  `zip_postal_code` varchar(15) DEFAULT NULL,\n  `country_region` varchar(50) DEFAULT NULL,\n  `web_page` longtext,\n  `notes` longtext,\n  `attachments` longblob,\n  PRIMARY KEY (`id`),\n  KEY `city` (`city`),\n  KEY `company` (`company`),\n  KEY `first_name` (`first_name`),\n  KEY `last_name` (`last_name`),\n  KEY `zip_postal_code` (`zip_postal_code`),\n  KEY `state_province` (`state_province`)\n) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;\nINSERT INTO `employees` (`id`, `company`, `last_name`, `first_name`, `email_address`, `job_title`, `business_phone`, `home_phone`, `mobile_phone`, `fax_number`, `address`, `city`, `state_province`, `zip_postal_code`, `country_region`, `web_page`, `notes`, `attachments`)\nVALUES\n    (10, 'Acme Industries', 'Smith', 'Mike', 'mike.smith@mail.com', 'Horticultarlist', '0118 9843212', NULL, NULL, NULL, '343 Friary Road', 'Manchester', 'Lancs.', 'M3 3DL', 'United Kingdom', NULL, NULL, NULL);\n\n```\n\n**Orders (Eloquent Model)**\n\n```php\n\u003c?php namespace App\\Model\\Database;\n\nuse Illuminate\\Database\\Eloquent\\Model;\n\nclass Orders extends Model\n{   \n    public $timestamps = false;\n    protected $table = 'orders';\n    protected $primaryKey = 'id';\n\n    /**\n     * @return \\Illuminate\\Database\\Eloquent\\Relations\\HasOne\n     */\n    public function employee()\n    {\n        return $this-\u003ebelongsTo(Employees::class, 'employee_id');\n    }\n}\n```\n\n**Orders SQL**\n\n```sql\nCREATE TABLE `orders` (\n  `id` int(11) NOT NULL AUTO_INCREMENT,\n  `employee_id` int(11) DEFAULT NULL,\n  `customer_id` int(11) DEFAULT NULL,\n  `order_date` datetime DEFAULT NULL,\n  `shipped_date` datetime DEFAULT NULL,\n  `shipper_id` int(11) DEFAULT NULL,\n  `ship_name` varchar(50) DEFAULT NULL,\n  `ship_address` longtext,\n  `ship_city` varchar(50) DEFAULT NULL,\n  `ship_state_province` varchar(50) DEFAULT NULL,\n  `ship_zip_postal_code` varchar(50) DEFAULT NULL,\n  `ship_country_region` varchar(50) DEFAULT NULL,\n  `shipping_fee` decimal(19,4) DEFAULT '0.0000',\n  `taxes` decimal(19,4) DEFAULT '0.0000',\n  `payment_type` varchar(50) DEFAULT NULL,\n  `paid_date` datetime DEFAULT NULL,\n  `notes` longtext,\n  `tax_rate` double DEFAULT '0',\n  `tax_status_id` tinyint(4) DEFAULT NULL,\n  `status_id` tinyint(4) DEFAULT '0',\n  PRIMARY KEY (`id`),\n  KEY `customer_id` (`customer_id`),\n  KEY `employee_id` (`employee_id`),\n  KEY `id` (`id`),\n  KEY `shipper_id` (`shipper_id`),\n  KEY `tax_status` (`tax_status_id`),\n  KEY `ship_zip_postal_code` (`ship_zip_postal_code`),\n  KEY `fk_orders_orders_status1` (`status_id`),  \n  CONSTRAINT `fk_orders_employees1` FOREIGN KEY (`employee_id`) REFERENCES `employees` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION\n) ENGINE=InnoDB AUTO_INCREMENT=82 DEFAULT CHARSET=utf8;\nINSERT INTO `orders` (`id`, `employee_id`, `customer_id`, `order_date`, `shipped_date`, `shipper_id`, `ship_name`, `ship_address`, `ship_city`, `ship_state_province`, `ship_zip_postal_code`, `ship_country_region`, `shipping_fee`, `taxes`, `payment_type`, `paid_date`, `notes`, `tax_rate`, `tax_status_id`, `status_id`)\nVALUES\n    (82, 10, NULL, '2015-03-12 00:00:00', '2015-03-12 00:00:00', NULL, NULL, '43, Borrowed Drive', 'New Oreleans', 'Louisiana', '4322', 'USA', 1.4000, 0.0000, NULL, NULL, NULL, 0, NULL, 0);\n\n```\n\nFollow up, we'll be creating Transformers. One Transformer is required for each class and it must implement the `\\NilPortugues\\Api\\Mappings\\JsonApiMapping` interface.\n\nWe will be placing these files at `app/Model/Api`:\n\n**EmployeesTransformer**\n\n```php\n\u003c?php namespace App\\Model\\Api;\n\nuse App\\Model\\Database\\Employees;\nuse NilPortugues\\Api\\Mappings\\JsonApiMapping;\n\nclass EmployeesTransformer implements JsonApiMapping\n{\n    /**\n     * Returns a string with the full class name, including namespace.\n     *\n     * @return string\n     */\n    public function getClass()\n    {\n        return Employees::class;\n    }\n\n    /**\n     * Returns a string representing the resource name \n     * as it will be shown after the mapping.\n     *\n     * @return string\n     */\n    public function getAlias()\n    {\n        return 'employee';\n    }\n\n    /**\n     * Returns an array of properties that will be renamed.\n     * Key is current property from the class. \n     * Value is the property's alias name.\n     *\n     * @return array\n     */\n    public function getAliasedProperties()\n    {\n        return [\n            'last_name' =\u003e 'surname',\n            \n        ];\n    }\n\n    /**\n     * List of properties in the class that will be  ignored by the mapping.\n     *\n     * @return array\n     */\n    public function getHideProperties()\n    {\n        return [\n            'attachments'\n        ];\n    }\n\n    /**\n     * Returns an array of properties that are used as an ID value.\n     *\n     * @return array\n     */\n    public function getIdProperties()\n    {\n        return ['id'];\n    }\n\n    /**\n     * Returns a list of URLs. This urls must have placeholders \n     * to be replaced with the getIdProperties() values.\n     *\n     * @return array\n     */\n    public function getUrls()\n    {\n        return [\n            'self' =\u003e ['name' =\u003e 'employees.show', 'as_id' =\u003e 'id'],\n            'employees' =\u003e ['name' =\u003e 'employees.index'],\n            'employee_orders' =\u003e ['name' =\u003e 'employees.orders', 'as_id' =\u003e 'id']\n        ];\n    }\n\n    /**\n     * Returns an array containing the relationship mappings as an array.\n     * Key for each relationship defined must match a property of the mapped class.\n     *\n     * @return array\n     */\n    public function getRelationships()\n    {\n        return [];\n    }\n} \n```\n\nSame goes for `Orders`,  these files will also be placed at `app/Model/Api`:\n\n**OrdersTransformer**\n\n```php\n\u003c?php namespace App\\Model\\Api;\n\nuse App\\Model\\Database\\Orders;\nuse NilPortugues\\Api\\Mappings\\JsonApiMapping;\n\nclass OrdersTransformer implements JsonApiMapping\n{\n    /**\n     * {@inheritDoc}\n     */\n    public function getClass()\n    {\n        return Orders::class;\n    }\n    /**\n     * {@inheritDoc}\n     */\n    public function getAlias()\n    {\n        return 'order';\n    }\n    /**\n     * {@inheritDoc}\n     */\n    public function getAliasedProperties()\n    {\n        return [];\n    }\n    /**\n     * {@inheritDoc}\n     */\n    public function getHideProperties()\n    {\n        return [];\n    }\n    /**\n     * {@inheritDoc}\n     */\n    public function getIdProperties()\n    {\n        return ['id'];\n    }\n    /**\n     * {@inheritDoc}\n     */\n    public function getUrls()\n    {\n        return [\n            'self'     =\u003e ['name' =\u003e 'orders.show', 'as_id' =\u003e 'id'],\n            'employee' =\u003e ['name' =\u003e 'employees.show', 'as_id' =\u003e 'employee_id'],\n        ];\n    }\n    /**\n     * {@inheritDoc}\n     */\n    public function getRelationships()\n    {\n        return [];\n    }\n    \n    /**\n     * List the fields that are mandatory in a persitence action (POST/PUT). \n     * If empty array is returned, all fields are mandatory.\n     */\n    public function getRequiredProperties()\n    {\n        return [];\n    }    \n} \n```\n\n\n#### Step 4: Usage\n\nCreate file `config/jsonapi.php`. This file should return an array returning all the class mappings.\n\n\n```php\n\u003c?php\nuse App\\Model\\Api\\EmployeesTransformer;\nuse App\\Model\\Api\\OrdersTransformer;\n\nreturn [\n    EmployeesTransformer::class,\n    OrdersTransformer::class,\n];\n```\n\u003cbr\u003e\n\n### Configuration for Lumen\n\n#### Step 1: Add the Service Provider\n\nOpen up `bootstrap/app.php`and add the following lines before the `return $app;` statement:\n\n```php\n$app-\u003eregister(\\NilPortugues\\Laravel5\\JsonApi\\Laravel5JsonApiServiceProvider::class);\n$app-\u003econfigure('jsonapi');\n```\n\nAlso, enable Facades by uncommenting:\n\n```php\n$app-\u003ewithFacades();\n```\n\n#### Step 2: Defining routes\n\nWe will be planning the resources ahead its implementation. All routes require to have a name.\n\nThis is how our `app/Http/routes.php` will look:\n\n\n```php\n\u003c?php\n$app-\u003egroup(\n    ['namespace' =\u003e 'Api'], function($app) {\n        $app-\u003eget(\n            'employees', [\n            'as' =\u003e 'employees.index',\n            'uses' =\u003e'EmployeesController@index'\n        ]);\n        $app-\u003epost(\n            'employees', [\n            'as' =\u003e 'employees.store',\n            'uses' =\u003e'EmployeesController@store'\n        ]);\n        $app-\u003eget(\n            'employees/{employee_id}', [\n            'as' =\u003e 'employees.show', \n            'uses' =\u003e'EmployeesController@show'\n        ]);\n        $app-\u003eput(\n            'employees/{employee_id}', [\n            'as' =\u003e 'employees.update', \n            'uses' =\u003e'EmployeesController@update'\n        ]);\n        $app-\u003epatch(\n            'employees/{employee_id}', [\n            'as' =\u003e 'employees.patch',\n            'uses' =\u003e'EmployeesController@update'\n        ]);\n        $app-\u003edelete(\n            'employees/{employee_id}', [\n            'as' =\u003e 'employees.destroy',\n            'uses' =\u003e'EmployeesController@destroy'\n        ]);\n        \n        $app-\u003eget(\n            'employees/{employee_id}/orders', [\n            'as' =\u003e 'employees.orders', \n            'uses' =\u003e 'EmployeesController@getOrdersByEmployee'\n        ]);\n    }\n);\n//...\n``` \n\n#### Step 3: Definition\n\nSame as Laravel 5.\n\n#### Step 4: Usage\n\nSame as Laravel 5.\n\n## JsonApiController\n\nWhether it's Laravel 5 or Lumen, usage is exactly the same. \n\nLet's create a new controller that extends the `JsonApiController` provided by this package, as follows:\n\n**Lumen users must extends from `LumenJsonApiController` not `JsonApiController`**.\n\n```php\n\u003c?php namespace App\\Http\\Controllers;\n\nuse App\\Model\\Database\\Employees;\nuse NilPortugues\\Laravel5\\JsonApi\\Controller\\JsonApiController;\n\nclass EmployeesController extends JsonApiController\n{\n    /**\n     * Return the Eloquent model that will be used \n     * to model the JSON API resources. \n     *\n     * @return \\Illuminate\\Database\\Eloquent\\Model\n     */\n    public function getDataModel()\n    {\n        return new Employees();\n    }\n}\n```\n\n\nIn case you need to overwrite any default behaviour, the **JsonApiController** methods are:\n\n```php\n//Constructor and defined actions\npublic function __construct(JsonApiSerializer $serializer);\npublic function listAction();\npublic function getAction(Request $request);\npublic function postAction(Request $request);\npublic function patchAction(Request $request);\npublic function putAction(Request $request);\npublic function deleteAction(Request $request);\n\n//Methods returning callables that access the persistence layer\nprotected function totalAmountResourceCallable();\nprotected function listResourceCallable();\nprotected function findResourceCallable(Request $request);\nprotected function createResourceCallable();\nprotected function updateResourceCallable();\n\n//Allows modification of the response object\nprotected function addHeaders(Response $response);\n```\n\n\nBut wait! We're missing out one action, `EmployeesController@getOrdersByEmployee`. \n\nAs the name suggests, it should list orders, so the behaviour should be the same as the one of `ListAction`.\n\nIf you look inside the `listAction`you'll find a code similar to the one below, but we just ajusted the behaviour and used it in our controller to support an additional action:\n\n```php\n\u003c?php namespace App\\Http\\Controllers;\n\nuse App\\Model\\Database\\Employees;\nuse App\\Model\\Database\\Orders;\nuse NilPortugues\\Laravel5\\JsonApi\\Controller\\JsonApiController;\n\nclass EmployeesController extends JsonApiController\n{\n    /**\n     * Return the Eloquent model that will be used \n     * to model the JSON API resources. \n     *\n     * @return \\Illuminate\\Database\\Eloquent\\Model\n     */\n    public function getDataModel()\n    {\n        return new Employees();\n    }    \n    \n    /**\n     * @param Request $request\n     *\n     * @return \\Symfony\\Component\\HttpFoundation\\Response\n     */\n    public function getOrdersByEmployee(Request $request)\n    {       \n        $apiRequest = RequestFactory::create();\n        $page = $apiRequest-\u003egetPage();\n\n        if (!$page-\u003esize()) {\n            $page-\u003esetSize(10); //Default elements per page\n        }\n\n        $resource = new ListResource(\n            $this-\u003eserializer,\n            $page,\n            $apiRequest-\u003egetFields(),\n            $apiRequest-\u003egetSort(),\n            $apiRequest-\u003egetIncludedRelationships(),\n            $apiRequest-\u003egetFilters()\n        );\n        \n        $totalAmount = function() use ($request) {\n            $id = (new Orders())-\u003egetKeyName();\n            return Orders::query()\n                -\u003ewhere('employee_id', '=', $request-\u003eemployee_id)\n                -\u003eget([$id])\n                -\u003ecount();\n        };\n\n        $results = function()  use ($request) {\n            return EloquentHelper::paginate(\n                $this-\u003eserializer,\n                Orders::query()\n                    -\u003ewhere('employee_id', '=', $request-\u003eemployee_id)\n            )-\u003eget();\n        };\n\n        $uri = route('employees.orders', ['employee_id' =\u003e $request-\u003eemployee_id]);\n        \n        return $resource-\u003eget($totalAmount, $results, $uri, Orders::class);\n    }\n}\n```\n\n\nAnd you're ready to go. Yes, it is **THAT** simple!\n\n\n## Examples: Consuming the API\n\n### GET\n\nThis is the output for `EmployeesController@getAction` being consumed from command-line method issuing: `curl -X GET \"http://localhost:9000/employees/1\"`.\n\n**Output:**\n\n```\nHTTP/1.1 200 OK\nCache-Control: private, max-age=0, must-revalidate\nContent-type: application/vnd.api+json\n```\n\n```json\n{\n    \"data\": {\n        \"type\": \"employee\",\n        \"id\": \"1\",\n        \"attributes\": {\n            \"company\": \"Northwind Traders\",\n            \"surname\": \"Freehafer\",\n            \"first_name\": \"Nancy\",\n            \"email_address\": \"nancy@northwindtraders.com\",\n            \"job_title\": \"Sales Representative\",\n            \"business_phone\": \"(123)555-0100\",\n            \"home_phone\": \"(123)555-0102\",\n            \"mobile_phone\": null,\n            \"fax_number\": \"(123)555-0103\",\n            \"address\": \"123 1st Avenue\",\n            \"city\": \"Seattle\",\n            \"state_province\": \"WA\",\n            \"zip_postal_code\": \"99999\",\n            \"country_region\": \"USA\",\n            \"web_page\": \"http://northwindtraders.com\",\n            \"notes\": null,\n            \"full_name\": \"Nancy Freehafer\"\n        },\n        \"links\": {\n            \"self\": {\n                \"href\": \"http://localhost:9000/employees/1\"\n            },\n            \"employee_orders\": {\n                \"href\": \"http://localhost:9000/employees/1/orders\"\n            }\n        },\n        \"relationships\": {\n            \"latest_orders\": [\n                {\n                    \"data\": {\n                        \"type\": \"order\",\n                        \"id\": \"71\"\n                    }\n                }\n            ]\n        }\n    },\n    \"included\": [        \n        {\n            \"type\": \"order\",\n            \"id\": \"71\",\n            \"attributes\": {\n                \"employee_id\": \"1\",\n                \"customer_id\": \"1\",\n                \"order_date\": \"2006-05-24 00:00:00\",\n                \"shipped_date\": null,\n                \"shipper_id\": \"3\",\n                \"ship_name\": \"Anna Bedecs\",\n                \"ship_address\": \"123 1st Street\",\n                \"ship_city\": \"Seattle\",\n                \"ship_state_province\": \"WA\",\n                \"ship_zip_postal_code\": \"99999\",\n                \"ship_country_region\": \"USA\",\n                \"shipping_fee\": \"0.0000\",\n                \"taxes\": \"0.0000\",\n                \"payment_type\": null,\n                \"paid_date\": null,\n                \"notes\": null,\n                \"tax_rate\": \"0\",\n                \"tax_status_id\": null,\n                \"status_id\": \"0\"\n            },\n            \"links\": {\n                \"self\": {\n                    \"href\": \"http://localhost:9000/orders/71\"\n                },\n                \"employee\": {\n                    \"href\": \"http://localhost:9000/employees/1\"\n                }\n            }\n        }\n    ],\n    \"links\": {\n        \"employees\": {\n            \"href\": \"http://localhost:9000/employees\"\n        },\n        \"employee_orders\": {\n            \"href\": \"http://localhost:9000/employees/1/orders\"\n        }\n    },\n    \"jsonapi\": {\n        \"version\": \"1.0\"\n    }\n}\n```\n\n\n### POST\n\nPOST requires all member attributes to be accepted, even those hidden by the mapper. \n\nFor instance, `attachments` member was hidden, but it is required, so it needs to be passed in with a valid value. On the other hand, `full_name` member value must not be passed in as an attribute or resource creation will fail.\n\nPassing and `id` is optional and will be used instead of a server-side generated value if provided.\n\nSending the following data to the server using `POST`to the following URI  `http://localhost:9000/employees`: \n\n```json\n{\n    \"data\": {\n        \"type\": \"employee\",\n        \"attributes\": {\n            \"company\": \"NilPortugues.com\",\n            \"surname\": \"Portugués\",\n            \"first_name\": \"Nil\",\n            \"email_address\": \"nilportugues@example.com\",\n            \"job_title\": \"Web Developer\",\n            \"business_phone\": \"(123)555-0100\",\n            \"home_phone\": \"(123)555-0102\",\n            \"mobile_phone\": null,\n            \"fax_number\": \"(123)555-0103\",\n            \"address\": \"Plaça Catalunya 1\",\n            \"city\": \"Barcelona\",\n            \"state_province\": \"Barcelona\",\n            \"zip_postal_code\": \"08028\",\n            \"country_region\": \"Spain\",\n            \"web_page\": \"http://nilportugues.com\",\n            \"notes\": null,\n            \"attachments\": null\n        }\n    }        \n}        \n```\n\nWill produce: \n\n```\nHTTP/1.1 201 Created\nCache-Control: private, max-age=0, must-revalidate\nContent-type: application/vnd.api+json\nLocation: http://localhost:9000/employees/10\n```\n\nNotice how 201 HTTP Status Code is returned and Location header too. Also `attachments` is not there anymore, and `full_name` was displayed.\n\n```json\n{\n    \"data\": {\n        \"type\": \"employee\",\n        \"id\": \"10\",\n        \"attributes\": {\n            \"company\": \"NilPortugues.com\",\n            \"surname\": \"Portugués\",\n            \"first_name\": \"Nil\",\n            \"email_address\": \"nilportugues@example.com\",\n            \"job_title\": \"Web Developer\",\n            \"business_phone\": \"(123)555-0100\",\n            \"home_phone\": \"(123)555-0102\",\n            \"mobile_phone\": null,\n            \"fax_number\": \"(123)555-0103\",\n            \"address\": \"Plaça Catalunya 1\",\n            \"city\": \"Barcelona\",\n            \"state_province\": \"Barcelona\",\n            \"zip_postal_code\": \"08028\",\n            \"country_region\": \"Spain\",\n            \"web_page\": \"http://nilportugues.com\",\n            \"notes\": null,\n            \"full_name\": \"Nil Portugués\"\n        },\n        \"links\": {\n            \"self\": {\n                \"href\": \"http://localhost:9000/employees/10\"\n            },\n            \"employee_orders\": {\n                \"href\": \"http://localhost:9000/employees/10/orders\"\n            }\n        }\n    },\n    \"links\": {\n        \"employees\": {\n            \"href\": \"http://localhost:9000/employees\"\n        },\n        \"employee_orders\": {\n            \"href\": \"http://localhost:9000/employees/10/orders\"\n        }\n    },\n    \"jsonapi\": {\n        \"version\": \"1.0\"\n    }\n}\n```\n\n### PUT\n\nPUT requires all member attributes to be accepted, just like POST.\n\nFor the sake of this example, we'll just send in a new `job_title` value, and keep everything else exactly the same.\n\nIt's important to notice this time we are required to pass in the `id`, even if it has been passed in by the URI, and of course the `id` values must match. Otherwise it will fail.\n\n\nSending the following data to the server using `PUT`to the following URI  `http://localhost:9000/employees/10`: \n\n```json\n{\n  \"data\": {\n    \"type\": \"employee\",\n    \"id\": 10,\n    \"attributes\": {\n      \"company\": \"NilPortugues.com\",\n      \"surname\": \"Portugués\",\n      \"first_name\": \"Nil\",\n      \"email_address\": \"nilportugues@example.com\",\n      \"job_title\": \"Full Stack Web Developer\",\n      \"business_phone\": \"(123)555-0100\",\n      \"home_phone\": \"(123)555-0102\",\n      \"mobile_phone\": null,\n      \"fax_number\": \"(123)555-0103\",\n      \"address\": \"Plaça Catalunya 1\",\n      \"city\": \"Barcelona\",\n      \"state_province\": \"Barcelona\",\n      \"zip_postal_code\": \"08028\",\n      \"country_region\": \"Spain\",\n      \"web_page\": \"http://nilportugues.com\",\n      \"notes\": null,\n      \"attachments\": null\n    }\n  }\n}\n```\n\n\nWill produce: \n\n```\nHTTP/1.1 200 OK\nCache-Control: private, max-age=0, must-revalidate\nContent-type: application/vnd.api+json\n```\n\n```json\n{\n    \"data\": {\n        \"type\": \"employee\",\n        \"id\": \"10\",\n        \"attributes\": {\n            \"company\": \"NilPortugues.com\",\n            \"surname\": \"Portugués\",\n            \"first_name\": \"Nil\",\n            \"email_address\": \"contact@nilportugues.com\",\n            \"job_title\": \"Full Stack Web Developer\",\n            \"business_phone\": \"(123)555-0100\",\n            \"home_phone\": \"(123)555-0102\",\n            \"mobile_phone\": null,\n            \"fax_number\": \"(123)555-0103\",\n            \"address\": \"Plaça Catalunya 1\",\n            \"city\": \"Barcelona\",\n            \"state_province\": \"Barcelona\",\n            \"zip_postal_code\": \"08028\",\n            \"country_region\": \"Spain\",\n            \"web_page\": \"http://nilportugues.com\",\n            \"notes\": null,\n            \"full_name\": \"Nil Portugués\"\n        },\n        \"links\": {\n            \"self\": {\n                \"href\": \"http://localhost:9000/employees/10\"\n            },\n            \"employee_orders\": {\n                \"href\": \"http://localhost:9000/employees/10/orders\"\n            }\n        }\n    },\n    \"included\": [],\n    \"links\": {\n        \"employees\": {\n            \"href\": \"http://localhost:9000/employees\"\n        },\n        \"employee_orders\": {\n            \"href\": \"http://localhost:9000/employees/10/orders\"\n        }\n    },\n    \"jsonapi\": {\n        \"version\": \"1.0\"\n    }\n}\n```\n\n\n### PATCH\n\nPATCH allows partial updates, unlike PUT. \n\nWe are required to pass in the `id` member, even if it has been passed in by the URI, and of course the `id` values must match. Otherwise it will fail.\n\nFor instance, sending the following data to the server using the following URI  `http://localhost:9000/employees/10`: \n\n```json\n{\n  \"data\": {\n    \"type\": \"employee\",\n    \"id\": 10,\n    \"attributes\": {\n      \"email_address\": \"contact@nilportugues.com\"\n    }\n  }\n}\n```\n\nWill produce: \n\n```\nHTTP/1.1 200 OK\nCache-Control: private, max-age=0, must-revalidate\nContent-type: application/vnd.api+json\n```\n\n```json\n{\n    \"data\": {\n        \"type\": \"employee\",\n        \"id\": \"10\",\n        \"attributes\": {\n            \"company\": \"NilPortugues.com\",\n            \"surname\": \"Portugués\",\n            \"first_name\": \"Nil\",\n            \"email_address\": \"contact@nilportugues.com\",\n            \"job_title\": \"Full Stack Web Developer\",\n            \"business_phone\": \"(123)555-0100\",\n            \"home_phone\": \"(123)555-0102\",\n            \"mobile_phone\": null,\n            \"fax_number\": \"(123)555-0103\",\n            \"address\": \"Plaça Catalunya 1\",\n            \"city\": \"Barcelona\",\n            \"state_province\": \"Barcelona\",\n            \"zip_postal_code\": \"08028\",\n            \"country_region\": \"Spain\",\n            \"web_page\": \"http://nilportugues.com\",\n            \"notes\": null,\n            \"full_name\": \"Nil Portugués\"\n        },\n        \"links\": {\n            \"self\": {\n                \"href\": \"http://localhost:9000/employees/10\"\n            },\n            \"employee_orders\": {\n                \"href\": \"http://localhost:9000/employees/10/orders\"\n            }\n        }\n    },\n    \"included\": [],\n    \"links\": {\n        \"employees\": {\n            \"href\": \"http://localhost:9000/employees\"\n        },\n        \"employee_orders\": {\n            \"href\": \"http://localhost:9000/employees/10/orders\"\n        }\n    },\n    \"jsonapi\": {\n        \"version\": \"1.0\"\n    }\n}\n```\n\n\n\n### DELETE\n\nDELETE is the easiest method to use, as it does not require body. Just issue a DELETE to `http://localhost:9000/employees/10/` and `Employee` with `id 10` will be gone.\n\nIt will produce the following output: \n\n```\nHTTP/1.1 204 No Content\nCache-Control: private, max-age=0, must-revalidate\nContent-type: application/vnd.api+json\n```\n\nAnd notice how response will be empty:\n\n```\n```\n\n\u003cbr\u003e\n\n## GET Query Params: include, fields, sort and page\n\nAccording to the standard, for GET method, it is possible to:\n- Show only those fields requested using `fields`query parameter.\n    - \u0026fields[resource]=field1,field2\n    \nFor instance, passing `/employees/10?fields[employee]=company,first_name` will produce the following output: \n\n```json\n{\n    \"data\": {\n        \"type\": \"employee\",\n        \"id\": \"10\",\n        \"attributes\": {\n            \"company\": \"NilPortugues.com\",\n            \"first_name\": \"Nil\"\n        },\n        \"links\": {\n            \"self\": {\n                \"href\": \"http://localhost:9000/employees/10\"\n            },\n            \"employee_orders\": {\n                \"href\": \"http://localhost:9000/employees/10/orders\"\n            }\n        }\n    },\n    \"links\": {\n        \"employees\": {\n            \"href\": \"http://localhost:9000/employees\"\n        },\n        \"employee_orders\": {\n            \"href\": \"http://localhost:9000/employees/10/orders\"\n        }\n    },\n    \"jsonapi\": {\n        \"version\": \"1.0\"\n    }\n}\n```\n    \n- Show only those `include` resources by passing in the relationship between them separated by dot, or just pass in list of resources separated by comma.\n    - \u0026include=resource1\n    - \u0026include=resource1.resource2,resource2.resource3\n    \n    \nFor instance, `/employees?include=order` will only load order type data inside `include` member, but `/employees?include=order.employee` will only load those orders related to the `employee` type.\n\n- Sort results using `sort` and passing in the member names of the main resource defined in `data[type]` member. If it starts with a `-` order is `DESCENDING`, otherwise it's `ASCENDING`.\n\n  - \u0026sort=field1,-field2\n  - \u0026sort=-field1,field2\n  \nFor instance: `/employees?sort=surname,-first_name`  \n\n- Pagination is also defined to allow doing page pagination, cursor pagination or offset pagination.\n  - \u0026page[number]\n  - \u0026page[limit]\n  - \u0026page[cursor]\n  - \u0026page[offset]\n  - \u0026page[size]\n  \nFor instance: `/employees?page[number]=1\u0026page[size]=10`  \n\n## POST/PUT/PATCH with Relationships\n\nThe JSON API allows resource creation and modification and passing in `relationships` that will create or alter existing resources too. \n\nLet's say we want to create a new `Employee` and pass in its first `Order`too. \n\nThis could be done issuing 2 `POST` to the end-points (one for Employee, one for Order) or pass in the first `Order` as a `relationship` with our `Employee`, for instance:\n\n```json\n{\n  \"data\": {\n    \"type\": \"employee\",\n    \"attributes\": {\n        \"company\": \"Northwind Traders\",\n        \"surname\": \"Giussani\",\n        \"first_name\": \"Laura\",\n        \"email_address\": \"laura@northwindtraders.com\",\n        \"job_title\": \"Sales Coordinator\",\n        \"business_phone\": \"(123)555-0100\",\n        \"home_phone\": \"(123)555-0102\",\n        \"mobile_phone\": null,\n        \"fax_number\": \"(123)555-0103\",\n        \"address\": \"123 8th Avenue\",\n        \"city\": \"Redmond\",\n        \"state_province\": \"WA\",\n        \"zip_postal_code\": \"99999\",\n        \"country_region\": \"USA\",\n        \"web_page\": \"http://northwindtraders.com\",\n        \"notes\": \"Reads and writes French.\",\n        \"full_name\": \"Laura Giussani\"\n    },    \n    \"relationships\": {\n      \"order\": {\n        \"data\": [\n          {\n            \"type\": \"order\",\n            \"attributes\": {\n              \"customer_id\": \"28\",\n              \"order_date\": \"2006-05-11 00:00:00\",\n              \"shipped_date\": \"2006-05-11 00:00:00\",\n              \"shipper_id\": \"3\",\n              \"ship_name\": \"Amritansh Raghav\",\n              \"ship_address\": \"789 28th Street\",\n              \"ship_city\": \"Memphis\",\n              \"ship_state_province\": \"TN\",\n              \"ship_zip_postal_code\": \"99999\",\n              \"ship_country_region\": \"USA\",\n              \"shipping_fee\": \"10.0000\",\n              \"taxes\": \"0.0000\",\n              \"payment_type\": \"Check\",\n              \"paid_date\": \"2006-05-11 00:00:00\",\n              \"notes\": null,\n              \"tax_rate\": \"0\",\n              \"tax_status_id\": null,\n              \"status_id\": \"0\"\n            }\n          }\n        ]\n      }\n    }    \n  }\n}       \n```\n\nDue to the existance of this use case, we'll have to ajust our Controller implementation overwriting some methods provided by the **JsonApiController**: `createResourceCallable`, `updateResourceCallable` and `patchResourceCallable`.\n\nHere's how it would be done for `createResourceCallable`.\n\n\n```php\n\u003c?php namespace App\\Http\\Controllers;\n\nuse App\\Model\\Database\\Employees;\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Support\\Facades\\DB;\nuse NilPortugues\\Api\\JsonApi\\Server\\Errors\\Error;\nuse NilPortugues\\Api\\JsonApi\\Server\\Errors\\ErrorBag;\nuse NilPortugues\\Laravel5\\JsonApi\\Controller\\JsonApiController;\n\nclass EmployeesController extends JsonApiController\n{\n    /**\n     * Now you can actually create Employee and Orders at once.\n     * Use transactions - DB::beginTransaction() for data integrity!\n     *\n     * @return callable\n     */\n    protected function createResourceCallable()\n    {\n        $createOrderResource = function (Model $model, array $data) {\n            if (!empty($data['relationships']['order']['data'])) {\n                $orderData = $data['relationships']['order']['data'];\n\n                if (!empty($orderData['type'])) {\n                    $orderData = [$orderData];\n                }\n\n                foreach ($orderData as $order) {\n                    $attributes = array_merge($order['attributes'], ['employee_id' =\u003e $model-\u003egetKey()]);\n                    Orders::create($attributes);\n                }\n            }\n        };\n\n        return function (array $data, array $values, ErrorBag $errorBag) use ($createOrderResource) {\n\n            $attributes = [];\n            foreach ($values as $name =\u003e $value) {\n                $attributes[$name] = $value;\n            }\n\n            if (!empty($data['id'])) {\n                $attributes[$this-\u003egetDataModel()-\u003egetKeyName()] = $values['id'];\n            }\n\n            DB::beginTransaction();\n            try {\n                $model = $this-\u003egetDataModel()-\u003ecreate($attributes);\n                $createOrderResource($model, $data);\n                DB::commit();\n                return $model;\n                \n            } catch(\\Exception $e) {\n                DB::rollback();\n                $errorBag[] = new Error('creation_error', 'Resource could not be created');\n                throw $e;\n            }\n        };\n    }\n\n}\n```\n\nIt is important, in order to use Transactions, do define in `Eloquent` models the `$fillable` values. \n\nHere's how `Employees` and `Orders` look like with `$fillable` defined.\n\n\n**Employees (Eloquent Model) with $fillable**\n```php\n\u003c?php namespace App\\Model\\Database;\n\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Foundation\\Validation\\ValidatesRequests;\n\nclass Employees extends Model\n{\n    public $timestamps = false;\n    protected $table = 'employees';    \n    protected $primaryKey = 'id';\n    protected $appends = ['full_name'];\n    \n    /**\n     * @var array\n     */\n    protected $fillable = [\n        'company',\n        'last_name',\n        'first_name',\n        'email_address',\n        'job_title',\n        'business_phone',\n        'home_phone',\n        'mobile_phone',\n        'fax_number',\n        'address',\n        'city',\n        'state_province',\n        'zip_postal_code',\n        'country_region',\n        'web_page',\n        'notes',\n        'attachments',\n    ];\n\n    /**\n     * @return \\Illuminate\\Database\\Eloquent\\Relations\\HasOne\n     */\n    public function latestOrders()\n    {\n        return $this-\u003ehasMany(Orders::class, 'employee_id')-\u003elimit(10);\n    }\n\n    /**\n     * @return string\n     */\n    public function getFullNameAttribute()\n    {\n        return $this-\u003efirst_name.' '.$this-\u003elast_name;\n    }\n}\n\n```\n\n\n**Orders (Eloquent Model) with $fillable**\n\n```php\n\u003c?php namespace App\\Model\\Database;\n\nuse Illuminate\\Database\\Eloquent\\Model;\n\nclass Orders extends Model\n{   \n    public $timestamps = false;\n    protected $table = 'orders';\n    protected $primaryKey = 'id';\n    \n    /**\n     * @var array\n     */\n    protected $fillable = [\n        'employee_id',\n        'customer_id',\n        'order_date',\n        'shipped_date',\n        'shipper_id',\n        'ship_name',\n        'ship_address',\n        'ship_city',\n        'ship_state_province',\n        'ship_zip_postal_code',\n        'ship_country_region',\n        'shipping_fee',\n        'taxes',\n        'payment_type',\n        'paid_date',\n        'notes',\n        'tax_rate',\n        'tax_status_id',\n        'status_id',\n    ];\n    \n    /**\n     * @return \\Illuminate\\Database\\Eloquent\\Relations\\HasOne\n     */\n    public function employee()\n    {\n        return $this-\u003ebelongsTo(Employees::class, 'employee_id');\n    }\n}\n```\n\n\n\n## Custom Response Headers\n\nAdding custom response headers can be done for multiple reasons: *versioning, setting expire headers, caching, setting private or public the served content...*\n\nIn order to do this, it's as simple as overwriting the JsonApiController `addHeaders` method. For instance, let's use the EmployeeController as an example: \n\n\n```php\n\u003c?php namespace App\\Http\\Controllers;\n\nuse App\\Model\\Database\\Employees;\nuse NilPortugues\\Laravel5\\JsonApi\\Controller\\JsonApiController;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass EmployeesController extends JsonApiController\n{\n    //All your supported methods...\n    \n    /**\n     * @param Response $response\n     *\n     * @return \\Symfony\\Component\\HttpFoundation\\Response\n     */\n    protected function addHeaders(Response $response) {\n        $response-\u003eheaders-\u003eset('X-API-Version', '1.0');\n        $response-\u003esetPublic();\n        $response-\u003esetMaxAge(60);\n        $response-\u003esetSharedMaxAge(60);\n\n        return $response;\n    }\n}    \n```\n\nNow all supported actions will include the added custom headers.\n\n# Common Errors and Solutions\n\n### \"Undefined index: @type\"\n\nThis usually happens because you did not write the namespace of your `Mapping` in `config/jsonapi.php`. \nDouble check, if missing, add it and refresh the resource. It should be gone!\n\n----\n\n## Contribute\n\nContributions to the package are always welcome!\n\n* Report any bugs or issues you find on the [issue tracker](https://github.com/nilportugues/laravel5-jsonapi-transformer/issues/new).\n* You can grab the source code at the package's [Git repository](https://github.com/nilportugues/laravel5-jsonapi-transformer).\n\n\n## Support\n\nGet in touch with me using one of the following means:\n\n - Emailing me at \u003ccontact@nilportugues.com\u003e\n - Opening an [Issue](https://github.com/nilportugues/laravel5-jsonapi-transformer/issues/new)\n\n## Authors\n\n* [Nil Portugués Calderó](http://nilportugues.com)\n* [The Community Contributors](https://github.com/nilportugues/laravel5-jsonapi-transformer/graphs/contributors)\n\n\n## License\nThe code base is licensed under the [MIT license](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnilportugues%2Flaravel5-jsonapi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnilportugues%2Flaravel5-jsonapi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnilportugues%2Flaravel5-jsonapi/lists"}