Skip to content

Force All API Requests to Accept JSON

Force all API Requests to accept JSON

Let’s say your APIs only provide responses in JSON format.

You want your APIs to serve only those clients who include the Accept: application/json header in their requests as this means they explicitly understand and want to receive responses in JSON format.

If a client doesn’t include this header, you want to send them an error message.

Laravel doesn’t handle this by default, so we’ll need to add this check ourselves.

🎥 Consider watching this video as well to supplement your learning:

Create a Middleware

A good way to implement the check is by using a Middleware.

A Middleware lets us apply the check to all APIs or just specific ones.

To create the Middleware, run the following command:

php artisan make:middleware ForceJsonResponse

In ForceJsonResponse Middleware we check if the client wants JSON.

If it doesn’t, we will return a Content-Type: text/plain response with a descriptive error message informing the client that they need to use the Accept: application/json header.

<?php

declare(strict_types = 1);

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class ForceJsonResponse
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        if (!$request->wantsJson()) {
            return response(
                'Unsupported request format. Set HTTP header to Accept: application/json',
                Response::HTTP_NOT_ACCEPTABLE
            );
        }

        return $next($request);
    }
}

We can now use the ForceJsonResponse Middleware in multipe ways.

Use Middleware on an API namespace

If we want to apply the Middleware to all the APIs under the api namespace, then we have to register it in the bootstrap/app.php file:

// bootstrap/app.php

<?php

use Illuminate\Foundation\Application;
use App\Http\Middleware\ForceJsonResponse;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->web(append: [
            \App\Http\Middleware\HandleInertiaRequests::class,
            \Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class,
        ]);

        // This 👇
        $middleware->api(append: [
            ForceJsonResponse::class
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();

If the APIs are protected by an authentication middleware, then you would want the ForceJsonResponse Middleware to be called first.

To do that, you should use prepend instead of append:

$middleware->api(prepend: [
    ForceJsonResponse::class
]);

Use Middleware in a Route group

If we want to use it as a route middleware group, then it would look like this:

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Middleware\ForceJsonResponse;
use Symfony\Component\HttpFoundation\Response;

Route::middleware([ForceJsonResponse::class])->group(function () {
    Route::post('/products', function (Request $request) {
        return response()->json([
            'message' => "We're requesting POST /api/products",
        ], Response::HTTP_OK);
    });

    Route::post('/customers', function (Request $request) {
        return response()->json([
            'message' => "We're requesting POST /api/customers",
        ], Response::HTTP_OK);
    });
});

Use Middleware only for one API

Or if we want to use the middleware only for one API, it would look like this:

<?php

use App\Http\Middleware\ForceJsonResponse;
use Symfony\Component\HttpFoundation\Response;

Route::post('/products', function (Request $request) {
    return response()->json([
        'message' => "We're requesting POST /api/products",
    ], Response::HTTP_OK);
})->middleware(ForceJsonResponse::class);

Result

Now all that’s left is to test the APIs and make sure they work properly.

In the image below, I use Postman to call an API to which I have applied the middleware.

Because I did not set the Accept: application/json header, the middleware intervenes and responds with an error message, notifying me that I need to specify the header in the request.

Thus, everything works as expected. ✅

Calling the API without the header results in an error.

In this example, I added the Accept: application/json header, and the API responded with the correct message. ✅

API responds in a JSON format because the header is sent in the request.

That was it. Happy coding 🥳


Let me know what you think about this article in the comments section below.

If you find this article helpful, please share it with others and subscribe to the blog to support me, and receive a bi-monthly-ish e-mail notification on my latest articles.   
  

Comments