I\'m trying to build a REST api using Laravel Framework, I want a way to force the API to always responed with JSON not by doing this manulaly like:
return Respo
To return JSON
in the controller just return $data;
For a JSON
response on errors, go to app\Exceptions\Handler.php
file and look at the render
method.
You should be able to re-write it to look something like this:
public function render($request, Exception $e)
{
// turn $e into an array.
// this is sending status code of 500
// get headers from $request.
return response()->json($e, 500);
}
However you will have to decide what to do with $e
, because it needs to be an array
. You can also set the status code and header array.
But then on any error, it will return a JSON
response.
Edit: It's also good to note that you can change the report
method to handle how laravel logs the error as well. More info here.
JsonResponseMiddleware
middleware.
php artisan make:middleware JsonResponseMiddleware
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\ResponseFactory;
class JsonResponseMiddleware
{
/**
* @var ResponseFactory
*/
protected $responseFactory;
/**
* JsonResponseMiddleware constructor.
*/
public function __construct(ResponseFactory $responseFactory)
{
$this->responseFactory = $responseFactory;
}
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
// First, set the header so any other middleware knows we're
// dealing with a should-be JSON response.
$request->headers->set('Accept', 'application/json');
// Get the response
$response = $next($request);
// If the response is not strictly a JsonResponse, we make it
if (!$response instanceof JsonResponse) {
$response = $this->responseFactory->json(
$response->content(),
$response->status(),
$response->headers->all()
);
}
return $response;
}
}
App\Http\Kernel.php
protected $middlewareGroups = [
'api' => [
...
....
/// Force to Json response (Our created Middleware)
\App\Http\Middleware\JsonResponseMiddleware::class,
],
'web' => [
...
....
/// Add Here as well if we want to force response in web routes too.
],
]
Now we will receive every response in JSON
only.
NOTE: Exceptions Too
Create a middleware as suggested by Alexander Lichter that sets the Accept
header on every request:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class ForceJsonResponse
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
$request->headers->set('Accept', 'application/json');
return $next($request);
}
}
Add it to $routeMiddleware
in the app/Http/Kernel.php
file:
protected $routeMiddleware = [
(...)
'json.response' => \App\Http\Middleware\ForceJsonResponse::class,
];
You can now wrap all routes that should return JSON:
Route::group(['middleware' => ['json.response']], function () { ... });
Give the json.response
middleware priority over other middlewares - to handle cases where the request is terminated by other middlewares (such as the Authorize
middleware) before you get to set the Accept
header.
To do this - override the constructor of you App\Http\Kernel
class (app/Http/Kernel.php
) with:
public function __construct( Application $app, Router $router ) {
parent::__construct( $app, $router );
$this->prependToMiddlewarePriority(\App\Http\Middleware\ForceJsonResponse::class);
}
I've used several mixed solutions also mentioned here to solve everything a bit more dynamic. The reason was here to always reply on every request below "/api" with a json response.
app/Http/Middleware/ForceJsonResponse.php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class ForceJsonResponse
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
// set Accept request header to application/json
$request->headers->set('Accept', 'application/json');
return $next($request);
}
}
app/Http/Kernel.php
protected $middlewareGroups = [
...
'api' => [
\App\Http\Middleware\ForceJsonResponse::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
...
];
app/Exceptions/Handler.php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
+ use Throwable;
class Handler extends ExceptionHandler
{
...
+ /**
+ * Render an exception into an HTTP response.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param \Throwable $e
+ * @return \Illuminate\Http\Response
+ */
+ public function render($request, Throwable $e)
+ {
+ // Force to application/json rendering on API calls
+ if ($request->is('api*')) {
+ // set Accept request header to application/json
+ $request->headers->set('Accept', 'application/json');
+ }
+
+ // Default to the parent class' implementation of handler
+ return parent::render($request, $e);
+ }
}
I know this has been answered but these are not good solutions because they change the status code in unpredictable ways. the best solution is to either add the appropriate headers so that Laravel returns JSON (I think its Accept: application/json
), or follow this great tutorial to just always tell Laravel to return JSON: https://hackernoon.com/always-return-json-with-laravel-api-870c46c5efb2
You could probably also do this through middleware as well if you wanted to be more selective or accommodate a more complex solution.
You can create an After Middleware and change structure of all responses
Middleware:
namespace App\Http\Middleware;
use Closure;
class ChangeResponseStructureMiddleware
{
public function handle($request, Closure $next)
{
$response = $next($request);
$newContent = [
'data' => $response->getOriginalContent(),
'context' => [
'code' => $response->getStatusCode()
]
];
return $response->setContent($newContent);
}
}
this middleware will force the response content to be like
{
"data": "response content of controller",
"context": {
"code": 200 // status code
}
}