Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

colq2's avatar
Level 2

Reuse Inertia Controller for API

I stumble across this problem again and again: How can I reuse my Inertia Controllers for my API? I saw bunch of posts on Laracasts asking the same question. And the answer is always: "Create a new controller for your API."

It felt always like writing the same code twice, because the controller already returning JSON response when Inertia is requesting data from the server (https://inertiajs.com/the-protocol).

I came up with a simple idea how to reuse the Inertia Controller for your API. I created a middleware that sets the 'X-Inertia' header. This forces Inertia to return a JSON response. The middleware take the response, changes the data structure a bit and returns it.

(Here is an example project: https://github.com/colq2/inertia-api)

The middleware is called InertiaToApiResponse and looks like this:

class InertiaToApiResponse
{
    public function handle(Request $request, Closure $next): Response
    {
        // forces inertia to create json response
        $request->headers->set('X-Inertia', true);

        // get response
        $response = $next($request);

        if ($response instanceof JsonResponse) {
            $data = $response->getData(true);

            // modify data and remove component
            Arr::forget($data, 'component');

            // set move props to data
$data['data'] = Arr::get($data, 'props');
            Arr::forget($data, 'props');

            // forget version
            Arr::forget($data, 'version');

            $response->setData($data);

            // remote X-Inertia header to not trigger version change response of inertia middleware
            $response->headers->remove('X-Inertia');
        }

        return $response;
    }
}

There is an api route with this middleware applied. You can find it in api.php:

Route::middleware(InertiaToApiResponse::class)->group(function () {
   Route::apiResource('posts', \App\Http\Controllers\PostController::class);
});

Which transforms the response from


{
  "component": "Posts/Index",
  "props": {
    "posts": {
      "data": []
    }
  },
  "url": "/posts",
  "version": "abcde"
}

to

{
  "data": {
    "posts": {
      "data": []
    }
  },
  "url": "/posts"
}

And we can call the api:

curl -X GET --location "http://inertia-api.test/api/posts" --http2 \
    -H "Accept: application/json" \
    -H "Content-Type: application/json"

Which returns perfect JSON. And we reused the controller, policies and so on.

Here are some more thoughts on this:

  • I think this can save time on smaller projects, bigger projects should separate the api
  • Work very good on resources. What about custom actions?
  • The middleware could have a parameter "pick" which is something like "posts.data", so the response only contain the array of data

Is it genius? Is it stupid? I would love to hear your thoughts on this!

0 likes
2 replies
LaryAI's avatar
Level 58

The approach you've described for reusing Inertia controllers for an API by using a middleware to manipulate the response is quite clever. It allows you to maintain a single source of truth for your data handling logic, which can be beneficial in smaller projects where the overhead of maintaining separate controllers for web and API routes is not justified.

Here are some thoughts on your approach:

  1. Maintainability: While this approach can save time initially, it may lead to confusion for developers who are new to the project or for those who expect a clear separation between API and web controllers. It's important to document this behavior clearly.

  2. Custom Actions: For custom actions that do not fit the standard resource controller methods, you might need to adjust the middleware to handle different response structures. This could add complexity to your middleware logic.

  3. Middleware Parameter: The idea of adding a "pick" parameter to the middleware to specify which part of the response to return is interesting. It would allow for more flexibility and could be implemented using dot notation to access nested data.

  4. Performance: There might be a slight performance overhead due to the additional processing in the middleware. However, this is likely negligible and would not be a concern for most applications.

  5. Testing: Ensure that your API endpoints are thoroughly tested, as the middleware adds an extra layer that transforms the response. This is crucial to ensure that the API behaves as expected.

  6. Versioning: Since you're removing the version key from the response, make sure that this does not interfere with Inertia's built-in mechanism for asset versioning and page reloads when assets change.

Here's an example of how you might implement the "pick" parameter in your middleware:

class InertiaToApiResponse
{
    public function handle(Request $request, Closure $next, $pick = null): Response
    {
        // forces inertia to create json response
        $request->headers->set('X-Inertia', true);

        // get response
        $response = $next($request);

        if ($response instanceof JsonResponse) {
            $data = $response->getData(true);

            // If a "pick" parameter is provided, only return that part of the data
            if ($pick) {
                $data = Arr::get($data, $pick);
            } else {
                // modify data and remove component
                Arr::forget($data, 'component');

                // set move props to data
                $data['data'] = $data['props'];
                Arr::forget($data, 'props');

                // forget version
                Arr::forget($data, 'version');
            }

            $response->setData($data);

            // remove X-Inertia header to not trigger version change response of inertia middleware
            $response->headers->remove('X-Inertia');
        }

        return $response;
    }
}

To use the "pick" parameter, you would modify your route definition like this:

Route::middleware('inertia.to.api.response:posts.data')->group(function () {
   Route::apiResource('posts', \App\Http\Controllers\PostController::class);
});

In conclusion, your approach is not stupid at all; it's a pragmatic solution for certain types of projects. However, it's important to weigh the benefits against potential drawbacks such as maintainability and clarity. If you decide to go this route, make sure it's well-documented and consider the points mentioned above.

colq2's avatar
Level 2

@LaryAI Haha Thanks Lary! I would love to hear what real people think of this šŸ¤“

Please or to participate in this conversation.