├── LICENSE ├── README.md ├── SECURITY.md ├── UPGRADING.md ├── composer.json └── src └── ApiResponseHelpers.php /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 F9 Web Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://banners.beyondco.de/Laravel%20API%20Response%20Helpers.png?theme=light&packageManager=composer+require&packageName=f9webltd%2Flaravel-api-response-helpers&pattern=brickWall&style=style_1&description=Generate+consistent+API+responses+for+your+Laravel+application&md=1&showWatermark=0&fontSize=100px&images=code) 2 | 3 | [![Run Tests - Current](https://github.com/f9webltd/laravel-api-response-helpers/actions/workflows/run-tests.yml/badge.svg)](https://github.com/f9webltd/laravel-api-response-helpers/actions/workflows/run-tests.yml) 4 | [![Packagist Version](https://img.shields.io/packagist/v/f9webltd/laravel-api-response-helpers?style=flat-square)](https://packagist.org/packages/f9webltd/laravel-api-response-helpers) 5 | [![Total Downloads](https://poser.pugx.org/f9webltd/laravel-api-response-helpers/downloads.png)](https://packagist.org/packages/f9webltd/laravel-api-response-helpers) 6 | [![Packagist PHP Version](https://img.shields.io/packagist/php-v/f9webltd/laravel-api-response-helpers?style=flat-square)](https://packagist.org/packages/f9webltd/laravel-api-response-helpers) 7 | [![Packagist License](https://img.shields.io/packagist/l/f9webltd/laravel-api-response-helpers?style=flat-square)](https://packagist.org/packages/f9webltd/laravel-api-response-helpers) 8 | 9 | # Laravel API Response Helpers 10 | 11 | A simple package allowing for consistent API responses throughout your Laravel application. 12 | 13 | ## Requirements 14 | 15 | - PHP `^8.0` 16 | - Laravel `^8.12`, `^9.0`, `^10.0`, `^11.0` or `^12.0` 17 | 18 | ### Legacy Support 19 | 20 | For PHP `^7.4` and Laravel `^6.0` / `^7.0` support, use package version [`^1.5`](https://github.com/f9webltd/laravel-api-response-helpers/tree/1.5.3) 21 | 22 | ## Installation / Usage 23 | 24 | ```bash 25 | composer require f9webltd/laravel-api-response-helpers 26 | ``` 27 | 28 | Simply reference the required trait within your controller: 29 | 30 | ```php 31 | respondWithSuccess(); 45 | } 46 | } 47 | ``` 48 | 49 | Optionally, the trait could be imported within a base controller. 50 | 51 | ## Available methods 52 | 53 | #### `respondNotFound(string|Exception $message, ?string $key = 'error')` 54 | 55 | Returns a `404` HTTP status code, an exception object can optionally be passed. 56 | 57 | #### `respondWithSuccess(array|Arrayable|JsonSerializable|null $contents = null)` 58 | 59 | Returns a `200` HTTP status code, optionally `$contents` to return as json can be passed. By default returns `['success' => true]`. 60 | 61 | #### `respondOk(string $message)` 62 | 63 | Returns a `200` HTTP status code 64 | 65 | #### `respondUnAuthenticated(?string $message = null)` 66 | 67 | Returns a `401` HTTP status code 68 | 69 | #### `respondForbidden(?string $message = null)` 70 | 71 | Returns a `403` HTTP status code 72 | 73 | #### `respondError(?string $message = null)` 74 | 75 | Returns a `400` HTTP status code 76 | 77 | #### `respondCreated(array|Arrayable|JsonSerializable|null $data = null)` 78 | 79 | Returns a `201` HTTP status code, with response optional data 80 | 81 | #### `respondNoContent(array|Arrayable|JsonSerializable|null $data = null)` 82 | 83 | Returns a `204` HTTP status code, with optional response data. Strictly speaking, the response body should be empty. However, functionality to optionally return data was added to handle legacy projects. Within your own projects, you can simply call the method, omitting parameters, to generate a correct `204` response i.e. `return $this->respondNoContent()` 84 | 85 | #### `setDefaultSuccessResponse(?array $content = null): self` 86 | 87 | Optionally, replace the default `['success' => true]` response returned by `respondWithSuccess` with `$content`. This method can be called from the constructor (to change default for all calls), a base API controller or place when required. 88 | 89 | `setDefaultSuccessResponse` is a fluent method returning `$this` allows for chained methods calls: 90 | 91 | ```php 92 | $users = collect([10, 20, 30, 40]); 93 | 94 | return $this->setDefaultSuccessResponse([])->respondWithSuccess($users); 95 | ``` 96 | 97 | Or 98 | ```php 99 | public function __construct() 100 | { 101 | $this->setDefaultSuccessResponse([]); 102 | } 103 | 104 | ... 105 | 106 | $users = collect([10, 20, 30, 40]); 107 | 108 | return $this->respondWithSuccess($users); 109 | ``` 110 | 111 | 112 | ## Use with additional object types 113 | 114 | In addition to a plain PHP `array`, the following data types can be passed to relevant methods: 115 | 116 | - Objects implementing the Laravel `Illuminate\Contracts\Support\Arrayable` contract 117 | - Objects implementing the native PHP `JsonSerializable` contract 118 | 119 | This allows a variety of object types to be passed and converted automatically. 120 | 121 | Below are a few common object types that can be passed. 122 | 123 | #### Laravel Collections - `Illuminate\Support\Collection` 124 | 125 | ```php 126 | $users = collect([10, 20, 30, 40]); 127 | 128 | return $this->respondWithSuccess($users); 129 | ``` 130 | 131 | #### Laravel Eloquent Collections - `Illuminate\Database\Eloquent\Collection` 132 | 133 | ```php 134 | $invoices = Invoice::pending()->get(); 135 | 136 | return $this->respondWithSuccess($invoices); 137 | ``` 138 | 139 | #### Laravel API Resources - `Illuminate\Http\Resources\Json\JsonResource` 140 | 141 | This package is intended to be used **alongside** Laravel's [API resources](https://laravel.com/docs/8.x/eloquent-resources) and in no way replaces them. 142 | 143 | ```php 144 | $resource = PostResource::make($post); 145 | 146 | return $this->respondCreated($resource); 147 | ``` 148 | 149 | ## Motivation 150 | 151 | Ensure consistent JSON API responses throughout an application. The motivation was primarily based on a very old inherited Laravel project. The project contained a plethora of methods/structures used to return an error: 152 | 153 | - `response()->json(['error' => $error], 400)` 154 | - `response()->json(['data' => ['error' => $error], 400)` 155 | - `response()->json(['message' => $error], Response::HTTP_BAD_REQUEST)` 156 | - `response()->json([$error], 400)` 157 | - etc. 158 | 159 | I wanted to add a simple trait that kept this consistent, in this case: 160 | 161 | `$this->respondError('Ouch')` 162 | 163 | ## Upgrading 164 | 165 | Please see [UPGRADING](UPGRADING.md) for details. 166 | 167 | ## Contribution 168 | 169 | Any ideas are welcome. Feel free to submit any issues or pull requests. 170 | 171 | ## Testing 172 | 173 | `composer test` 174 | 175 | ## Security 176 | 177 | If you discover any security related issues, please email rob@f9web.co.uk instead of using the issue tracker. 178 | 179 | ## Credits 180 | 181 | - [Rob Allport](https://github.com/ultrono) for [F9 Web Ltd.](https://www.f9web.co.uk) 182 | - [All Contributors](https://github.com/f9webltd/laravel-api-response-helpers/graphs/contributors) 183 | 184 | ## License 185 | 186 | The MIT License (MIT). Please see [License File](LICENSE) for more information. 187 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | If you discover any security related issues, please email rob@f9web.co.uk as opposed to using the issue tracker. 4 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | # Upgrading 2 | 3 | ## From 1.x to 2.x 4 | 5 | - Run the following command to fetch the latets version of the package: `composer require f9webltd/laravel-api-response-helpers:^2.0` 6 | - The package now requires PHP `^8.0` and Laravel `^8.12` / `^9.0`, `^10.0` or `^11.0` 7 | - No further changes are required, the API remains the same 8 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "f9webltd/laravel-api-response-helpers", 3 | "description": "A super simple package allowing for consistent API responses throughout your Laravel application ", 4 | "keywords": [ 5 | "laravel", 6 | "laravel api response", 7 | "laravel api response helpers" 8 | ], 9 | "homepage": "https://github.com/f9webltd/laravel-api-response-helpers", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Rob Allport", 14 | "email": "rob@f9web.co.uk", 15 | "homepage": "https://www.f9web.co.uk", 16 | "role": "Developer" 17 | } 18 | ], 19 | "require": { 20 | "php": "^8.0", 21 | "illuminate/support": "^8.12|^9.0|^10.0|^11.0|^12.0" 22 | }, 23 | "require-dev": { 24 | "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0|^10.0", 25 | "phpunit/phpunit": "^9.4|^10.1|^11.5.3" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "F9Web\\": "src" 30 | } 31 | }, 32 | "autoload-dev": { 33 | "psr-4": { 34 | "F9Web\\ApiResponseHelpers\\Tests\\": "tests" 35 | } 36 | }, 37 | "scripts": { 38 | "test": "./vendor/bin/phpunit" 39 | }, 40 | "config": { 41 | "sort-packages": true 42 | }, 43 | "minimum-stability": "dev", 44 | "prefer-stable": true 45 | } 46 | -------------------------------------------------------------------------------- /src/ApiResponseHelpers.php: -------------------------------------------------------------------------------- 1 | true, 19 | ]; 20 | 21 | public function respondNotFound( 22 | string|Exception $message, 23 | ?string $key = 'error' 24 | ): JsonResponse { 25 | return $this->apiResponse( 26 | data: [$key => $this->morphMessage($message)], 27 | code: Response::HTTP_NOT_FOUND 28 | ); 29 | } 30 | 31 | public function respondWithSuccess( 32 | array|Arrayable|JsonSerializable|null $contents = null 33 | ): JsonResponse { 34 | $contents = $this->morphToArray(data: $contents) ?? []; 35 | 36 | $data = [] === $contents 37 | ? $this->_api_helpers_defaultSuccessData 38 | : $contents; 39 | 40 | return $this->apiResponse(data: $data); 41 | } 42 | 43 | public function setDefaultSuccessResponse(?array $content = null): self 44 | { 45 | $this->_api_helpers_defaultSuccessData = $content ?? []; 46 | return $this; 47 | } 48 | 49 | public function respondOk(string $message): JsonResponse 50 | { 51 | return $this->respondWithSuccess(contents: ['success' => $message]); 52 | } 53 | 54 | public function respondUnAuthenticated(?string $message = null): JsonResponse 55 | { 56 | return $this->apiResponse( 57 | data: ['error' => $message ?? 'Unauthenticated'], 58 | code: Response::HTTP_UNAUTHORIZED 59 | ); 60 | } 61 | 62 | public function respondForbidden(?string $message = null): JsonResponse 63 | { 64 | return $this->apiResponse( 65 | data: ['error' => $message ?? 'Forbidden'], 66 | code: Response::HTTP_FORBIDDEN 67 | ); 68 | } 69 | 70 | public function respondError(?string $message = null): JsonResponse 71 | { 72 | return $this->apiResponse( 73 | data: ['error' => $message ?? 'Error'], 74 | code: Response::HTTP_BAD_REQUEST 75 | ); 76 | } 77 | 78 | public function respondCreated( 79 | array|Arrayable|JsonSerializable|null $data = null 80 | ): JsonResponse { 81 | $data ??= []; 82 | 83 | return $this->apiResponse( 84 | data: $this->morphToArray(data: $data), 85 | code: Response::HTTP_CREATED 86 | ); 87 | } 88 | 89 | public function respondFailedValidation( 90 | string|Exception $message, 91 | ?string $key = 'message' 92 | ): JsonResponse { 93 | return $this->apiResponse( 94 | data: [$key => $this->morphMessage($message)], 95 | code: Response::HTTP_UNPROCESSABLE_ENTITY 96 | ); 97 | } 98 | 99 | public function respondTeapot(): JsonResponse 100 | { 101 | return $this->apiResponse( 102 | data: ['message' => 'I\'m a teapot'], 103 | code: Response::HTTP_I_AM_A_TEAPOT 104 | ); 105 | } 106 | 107 | public function respondNoContent( 108 | array|Arrayable|JsonSerializable|null $data = null 109 | ): JsonResponse { 110 | $data ??= []; 111 | $data = $this->morphToArray(data: $data); 112 | 113 | return $this->apiResponse( 114 | data: $data, 115 | code: Response::HTTP_NO_CONTENT 116 | ); 117 | } 118 | 119 | private function apiResponse(array $data, int $code = 200): JsonResponse 120 | { 121 | return response()->json(data: $data, status: $code); 122 | } 123 | 124 | private function morphToArray(array|Arrayable|JsonSerializable|null $data): ?array 125 | { 126 | if ($data instanceof Arrayable) { 127 | return $data->toArray(); 128 | } 129 | 130 | if ($data instanceof JsonSerializable) { 131 | return $data->jsonSerialize(); 132 | } 133 | 134 | return $data; 135 | } 136 | 137 | private function morphMessage(string|Exception $message): string 138 | { 139 | return $message instanceof Exception 140 | ? $message->getMessage() 141 | : $message; 142 | } 143 | } 144 | --------------------------------------------------------------------------------