Laravue's avatar

Globally Append Meta data to Api Resources

I want to have it so in certain situations when I return any class of resource top level meta data is appended by default.

So here's an example of some meta data I'd want to be added globally and how I can currently add it for every response individually.

public function show($id)
    {
        $resource = new UserResource(User::findOrFail($id));


        if (App::environment('local')) {
            $queryLog = DB::getQueryLog();
            $resource->additional([
                'meta' => [
                    'queries' => [
                        'count' => count($queryLog),
                        'log'   => $queryLog,
                    ],
                ],
            ]);
        }

        return $resource;
    }

Here I'm checking if I'm in a local/development environment and I'm appending the query log to the meta data for this resource.

This will return something akin to

"data": [
    ...
],
"meta": {
        "queries": {
            "count": 14,
            "log": [
                {
                    "query": "select * from `users` where `users`.`id` in (?) and `users`.`deleted_at` is null",
                    "bindings": [
                        1
                    ],
                    "time": 1.27
                },
        {
            ...
               

However the obvious problem here is I'd need to add the above conditional in every controller action which is cumbersome.

Any way to have this added globally for all resources?

0 likes
4 replies
burlresearch's avatar

You could just extend Illuminate\Http\Resources\Json\JsonResource in your own subclass in your Resources and put the logic there.

Then foreach of your API Resources, just extend that class which would take care of it for all your resources, in 1 place.

Laravue's avatar

I did try that actually but what's the best method to override in the subclass?

I tried overriding the with() and additional() methods however the JsonResource class didn't seem to run them though unless you added the function in the controller so no improvement.

This also does mean, like you say, you have to add the Subclass for every resource you make. Ideally you'd be able to php artisan make:resource and not have to then remember to edit it each time.

tykus's avatar

You could use a middleware to append to the response with your meta data:

php artisan make:middleware ResponseMetaMiddleware

Inside the handle method, you can intercept the response and append to the existing data like this:

public function handle($request, Closure $next)
{
    $response = $next($request);

    if ($response instanceof JsonResponse) {
        $payloadWithMeta = array_merge(
            json_decode($response->getContent(), true), // the original response data
            'meta' => $this->getMetaData()
        );
        
        $response->setData($payloadWithMeta);

        return $response;
    }
}

// a method that fetches the relevant meta data (from a service??) and returns an array
private function getMetaData()
{
    return // an array
}

Apply the middleware globally, or as a route middleware in app/Http/Kernel.php, e.g. globally applied:

// app/Http/Kernel.php

    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array
     */
    protected $middleware = [
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
        \App\Http\Middleware\TrustProxies::class,
        \App\Http\Middleware\ResponseMetaMiddleware::class,
    ];
2 likes

Please or to participate in this conversation.