How do you force a JSON response on every response in Laravel?

后端 未结 6 2063
面向向阳花
面向向阳花 2021-02-13 14:31

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         


        
相关标签:
6条回答
  • 2021-02-13 15:19

    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.

    0 讨论(0)
  • 2021-02-13 15:25

    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. Registor 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.

    NOTE: Exceptions Too

    0 讨论(0)
  • 2021-02-13 15:26

    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);
        }
    
    0 讨论(0)
  • 2021-02-13 15:27

    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);
    +    }
    
    }
    
    0 讨论(0)
  • 2021-02-13 15:33

    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.

    0 讨论(0)
  • 2021-02-13 15:38

    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
       }
    }
    
    0 讨论(0)
提交回复
热议问题