25

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 Response::json($data);

In other words I want every response to be JSON. Is there a good way to do that?

Update: The response must be JSON even on exceptions like not found exception.

RattleyCooper
  • 4,997
  • 5
  • 27
  • 43
Mustafa Dwaikat
  • 3,392
  • 9
  • 27
  • 41

7 Answers7

42

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 () { ... });

Edit: For Laravel 6.9+

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);
    }
Yaron U.
  • 7,681
  • 3
  • 31
  • 45
M165437
  • 897
  • 1
  • 10
  • 18
  • This method is cleanest! We can also add `Accept: application/json` in client-side but it's more consistent this way – Nickson Yap Jun 13 '20 at 14:23
  • You'll also need to give this middleware priority in case that other middleware terminated the request before that middleware took action. To do this you should add `$this->prependToMiddlewarePriority(\App\Http\Middleware\ForceJsonResponse::class);` to the constructor of you `Http\Kernel` class – Yaron U. Sep 08 '20 at 14:05
  • Edited the answer with the priority thing - This should definitely be the correct answer – Yaron U. Sep 08 '20 at 14:12
  • 1
    I had better luck in Laravel 8+ prepending the rule to a group. In my case the API middleware group. `public function __construct( Application $app, Router $router ) { parent::__construct( $app, $router ); $this->prependMiddlewareToGroup('api',\App\Http\Middleware\ForceJsonResponse::class); }` – alloyking Jun 03 '21 at 18:47
  • Wouldn't it be sufficient to add this middle ware to the `api` group? As far as I understand (but I might be wrong) you can priotize the middleware by placing it as the first item? https://laravel.com/docs/9.x/middleware#middleware-groups – NicoHood May 27 '22 at 16:53
  • Also a hint: Create this new class easily via `php artisan make:middleware ForceJsonResponse` and replace the content. – NicoHood May 27 '22 at 16:57
14

Laravel Middleware is Extremely useful in this use case.

1. Make 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;
    }
}

2. Register middleware in 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.

Please note that: Even exceptions will respond in JSON format

danronmoon
  • 3,814
  • 5
  • 34
  • 56
dipenparmar12
  • 3,042
  • 1
  • 29
  • 39
  • 2
    Using middleware is better approach than returning response from constructor. Thanks dear. Your solution worked for me. – Kamlesh Jul 12 '21 at 07:33
  • 1
    https://stackoverflow.com/a/75492554/14344959 – Harsh Patel Mar 06 '23 at 12:01
  • 1
    I think some methods have been renamed in the `JsonResponse` instance. for example `$response->content()` is now `$response->getContent()` and `$response->status()` is now `$response->getStatusCode()` – abe Apr 18 '23 at 08:25
  • Thanks @abe for your attention to the detail. – dipenparmar12 Apr 19 '23 at 06:08
12

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.

JonTroncoso
  • 791
  • 1
  • 8
  • 22
11

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.

RattleyCooper
  • 4,997
  • 5
  • 27
  • 43
  • This will return the row xdeubg message and I need to have the laravel debug response – Mustafa Dwaikat Apr 08 '16 at 18:25
  • @MustafaDwekat, I'm guessing it ended up working? Did you need to change anything from my answer? If so, let me know and I'll update it. – RattleyCooper Apr 08 '16 at 18:50
  • After playing there for a while I actually can get whatever I want from it. Thanks a lot. that was helpful. – Mustafa Dwaikat Apr 08 '16 at 18:51
  • 1
    Puncher That was great I miss undersod it but I wanna add that you need to form your error response before you return like `$response = ['error_code'=> $e->getStatusCode() , 'trace'=>$e->getTrace()]` then return that. – Mustafa Dwaikat Apr 08 '16 at 19:05
  • @MustafaDwekat, ah, yes. You have to turn your `Exception` into an array before passing it. Glad it worked out. Check out the [docs on the exception handler](https://laravel.com/docs/5.2/errors#the-exception-handler) for more info on what you can do with the exception handler. If you need these errors logged a certain way, you can control that as well by modifying the `report` method. Cheers! – RattleyCooper Apr 08 '16 at 19:16
  • I found this answer useful for using this in conjunction with filtering by route (which I needed to force API responses to be JSON): https://stackoverflow.com/a/30052319/2053165 – Leith Dec 22 '17 at 06:24
9

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.

  1. Create a Middleware to force JSON Output in 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);
    }
}

  1. Add this new middleware on TOP of the api array in app/Http/Kernel.php
    protected $middlewareGroups = [
        ...

        'api' => [
            \App\Http\Middleware\ForceJsonResponse::class,
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        ...
    ];

  1. Overwrite the render method of the Exception handler that all exceptions also response with JSON 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);
+    }

}
bkuebler
  • 91
  • 1
  • 1
1

Another simple solution is extending the Request class. Create app/Http/Request.php with the following

<?php

namespace App\Http;

use Illuminate\Http\Request as BaseRequest;
use Illuminate\Support\Str;

class Request extends BaseRequest
{
    public function wantsJson(): bool
    {
        return Str::startsWith($this->path(), 'api/') || parent::wantsJson();
    }
}

and then in public/index.php add:

use App\Http\Request;
Harry
  • 253
  • 5
  • 11
Rafał G.
  • 1,529
  • 22
  • 35
0

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
   }
}
Amin Shojaei
  • 5,451
  • 2
  • 38
  • 46