Laravel: Convert all responses to JSON automatically
Because not every person in the world wants HTML, nor use response()->json()
in every freaking controller.
If you are doing something like an API, a JSON response is almost mandatory. The problem lies when the client doesn’t issue a Request telling explicitly to your app that accepts JSON as a response. When that occurs, your app will nonchalantly send a HTML response, which is not cool.
The Middleware
Well, there is very simple way to “force” your app to return JSON, and that is with one line middleware. We will call it JsonMiddleware
because indicates the response should be JSON, but doesn’t forces it.
<?php
namespace App\Http\Middleware;
use Closure;
class JsonMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$request->headers->set('Accept', 'application/json');
return $next($request);
}
}
What this does is modify the incoming request and set (or replace) the “Accept” header.
When the app is ready to send a Response, it will check if the Request wanted JSON, and this is done by looking if the Accept
header is application/json
or any other JSON-like header. So, the application will try to push a JSON response automatically, when possible.
The place
In our App\Http\Kernel.php
, we can find the list of middlewares the App manages.
Since we want this to run only in our API routes, we will register the middleware under the api group, along with throttler and bindings.
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
// ...
],
'api' => [
'throttle:60,1',
'bindings',
// Our new JSON Middleware
\App\Http\Middleware\Api\JsonMiddleware::class,
],
];
If we need more control of where we can put it, we can register an alias like return-json
in the $routeMiddleware
.
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array
*/
protected $routeMiddleware = [
// ...
// Register the alias for the JSON middleware
'return-json' => \App\Http\Middleware\Api\JsonMiddleware::class,
];
Then in our routes invoke the middleware where we need.
Route::namespace('User')
->middleware(['api', 'return-json']) // Use our JSON Middleware
->group(function () {
Route::get('user/profile', 'UserController@profile');
Route::get('user/calls', 'UserController@calls');
);
The Order
Laravel conveniently has a global order for middlewares. When they are called in a route, it will run them in the same order displayed on $middlewarePriority
.
We want this to start first because is what the client should have set in the first place. This will allow, for example, to not return a redirect when the authentication fails since it doesn’t do it when it checks if the request should return JSON. Thanks to this order, the “Should return JSON” flag is set way before we hit the Authentication middleware condition.
/**
* The priority-sorted list of middleware.
*
* This forces the listed middleware to always be in the given order.
*
* @var array
*/
protected $middlewarePriority = [ // Put the JSON Middleware first
\App\Http\Middleware\Api\JsonMiddleware::class, // Then the rest...
];
And that’s it.
Bonus: Forcing a JSON Response
Additionally to our JsonMiddleware
, we can ensure the response is a Illuminate\Http\JsonResponse
instance.
We will modify the middleware by calling the ResponseFactory
our app is using to create the response with automatic injection, and through it make the JSON response.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Http\JsonResponse;
class JsonMiddleware
{
/**
* The Response Factory our app uses
*
* @var ResponseFactory
*/
protected $factory;
/**
* JsonMiddleware constructor.
*
* @param ResponseFactory $factory
*/
public function __construct(ResponseFactory $factory)
{
$this->factory = $factory;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($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->factory->json(
$response->content(),
$response->status(),
$response->headers->all()
);
}
return $response;
}
}
And you can even use a fourth parameter to set the type of JSON encoding for the resulting response. Neat!