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

unlikenesses's avatar

Caching API Resources

I am using API Resources to generate responses for my API. My issue is that they're rather large beasts: if I dd them I see they contain lots of metadata which is not finally visible in the JSON that's returned. Also, when I cache them, all of this metadata is cached, which pushes up my Redis memory usage.

Is there a way to take an API Resource and strip it of its metadata so as to cache only the JSON?

0 likes
17 replies
munazzil's avatar

You can use customize this function in above all function in controller.

function seoUrl($string) {

    $string = trim($string); // Trim String

    $string = preg_replace("/[^a-z0-9_\s-]/", "", $string);  //Strip any unwanted characters

    return $string;

}
devfrey's avatar

Where and how are you trying to cache the resources? If you specifically want to cache the JSON, you'll want to store the result from the Resource's toArray() or toJson() methods, rather than the entire object. If you could share some code related to this problem, it might be easier to assist.

unlikenesses's avatar

@DEVFREY - Thanks - I'm using Redis, and the Cache facade, in my controller:

public function index()
{
    $cacheKey = md5('cars');
    return Cache::remember($cacheKey, 30, function() {
        return CarResource::collection(Car::all());
    });
}
devfrey's avatar

You should be fine using jsonSerialize() on the collection. This way you won't have any related objects/properties serialized into the cache.

public function index()
{
    $cacheKey = md5('cars');
    return Cache::remember($cacheKey, 30, function() {
        return CarResource::collection(Car::all())->jsonSerialize();
    });
}

Assuming index() is a controller method, Laravel will convert the resulting array from the cache to a JSON response.

Edit: replaced toArray() with jsonSerialize().

1 like
unlikenesses's avatar

@DEVFREY - That gives me the error message:

Too few arguments to function Illuminate\Http\Resources\Json\ResourceCollection::toArray(), 0 passed

unlikenesses's avatar

@DEVFREY - That half-works, thank you. Unfortunately it only seems to work for the top level - if an API resource contains nested API resources, they remain unserialized... :-(

devfrey's avatar

@UNLIKENESSES - That's strange. It's supposed to work recursively.

Could you perhaps share the output?

unlikenesses's avatar

@DEVFREY - Here's a bit of it. You'll see that the root level is just key / value pairs, then when it gets to the thumbnail property (which is another API Resource), it shows everything:

array:6 [
  0 => array:16 [
    "id" => 1
    "title" => "Test"
    "subtitle" => "Lorem ipsum dolor sit amet con",
    "thumbnail" => ImageResource {#1897
      +resource: Image {#846
        +with: array:3 [
          0 => "urls"
          1 => "filters"
          2 => "metadata"
        ]
        #fillable: array:4 [
          0 => "original_name"
          1 => "date"
          2 => "date_string"
          3 => "weight"
        ]
        +translatable: array:1 [
          0 => "date_string"
        ]
        #touches: array:1 [
          0 => "galleries"
        ]
        #connection: "mysql"
        #table: "images"
        #primaryKey: "id"
        #keyType: "int"
        +incrementing: true
        #withCount: []
        #perPage: 15
        +exists: true
        +wasRecentlyCreated: false
        #attributes: array:9 [
          "id" => 1
          "original_name" => "b-tor2016.jpg"
          "date" => null
          "date_string" => null
          "weight" => null
          "created_by" => null
          "updated_by" => null
          "created_at" => "2019-02-18 09:47:50"
          "updated_at" => "2019-02-18 09:47:53"
        ]
        #original: array:9 [
          "id" => 1
          "original_name" => "b-tor2016.jpg"
          "date" => null
          "date_string" => null
          "weight" => null
          "created_by" => null
          "updated_by" => null
          "created_at" => "2019-02-18 09:47:50"
          "updated_at" => "2019-02-18 09:47:53"
        ]
        #changes: []
        #casts: []
        #dates: []
        #dateFormat: null
        #appends: []
        #dispatchesEvents: []
        #observables: []
        #relations: array:3 [
          "urls" => Collection {#874
            #items: array:3 [
              0 => ImageUrl {#901
                #fillable: array:5 [ …5]
                #connection: "mysql"
                #table: "image_urls"
                #primaryKey: "id"
                #keyType: "int"
                +incrementing: true
                #with: []
                #withCount: []
                #perPage: 15
                +exists: true
                +wasRecentlyCreated: false
                #attributes: array:8 [ …8]
                #original: array:8 [ …8]
                #changes: []
                #casts: []
                #dates: []
                #dateFormat: null
                #appends: []
                #dispatchesEvents: []
                #observables: []
                #relations: []
                #touches: []
                +timestamps: true
                #hidden: []
                #visible: []
                #guarded: array:1 [ …1]
              }
              1 => ImageUrl {#902
                #fillable: array:5 [ …5]
                #connection: "mysql"
                #table: "image_urls"
                #primaryKey: "id"
                #keyType: "int"
                +incrementing: true
                #with: []
                #withCount: []
                #perPage: 15
                +exists: true
                +wasRecentlyCreated: false
                #attributes: array:8 [ …8]
                #original: array:8 [ …8]
                #changes: []
                #casts: []
                #dates: []
                #dateFormat: null
                #appends: []
                #dispatchesEvents: []
                #observables: []
                #relations: []
                #touches: []
                +timestamps: true
                #hidden: []
                #visible: []
                #guarded: array:1 [ …1]
              }
              2 => ImageUrl {#903
                #fillable: array:5 [ …5]
                #connection: "mysql"
                #table: "image_urls"
                #primaryKey: "id"
                #keyType: "int"
                +incrementing: true
                #with: []
                #withCount: []
                #perPage: 15
                +exists: true
                +wasRecentlyCreated: false
                #attributes: array:8 [ …8]
                #original: array:8 [ …8]
                #changes: []
                #casts: []
                #dates: []
                #dateFormat: null
                #appends: []
                #dispatchesEvents: []
                #observables: []
                #relations: []
                #touches: []
                +timestamps: true
                #hidden: []
                #visible: []
                #guarded: array:1 [ …1]
              }
            ]
          }
          "filters" => Collection {#806
            #items: []
          }
          "metadata" => Metadata {#970
            #table: "content_metadata"
            +translatable: array:2 [
              0 => "title"
              1 => "description"
            ]
            #fillable: array:6 [
              0 => "title"
              1 => "description"
              2 => "author"
              3 => "copyright"
              4 => "ticket_link"
              5 => "expires"
            ]
            #touches: array:1 [
              0 => "content"
            ]
            #connection: "mysql"
            #primaryKey: "id"
            #keyType: "int"
            +incrementing: true
            #with: []
            #withCount: []
            #perPage: 15
            +exists: true
            +wasRecentlyCreated: false
            #attributes: array:11 [
              "id" => 1
              "title" => "{"en":"CURTSEYING as you're falling.","de":"Teil der Zeit, um zu."}"
              "description" => "{"en":"Alice could think of any that do,' Alice said very politely, feeling quite pleased to find that.","de":"Amte steht, und sagte zu ihm: 'feiner junger Herr! Lieben ist menschlich, nur m\u00fc\u00dft Ihr menschlich."}"
              "author" => "Elsbeth Jakob"
              "copyright" => "Herr Prof. Dr. Stefan Lenz B.Eng."
              "ticket_link" => "http://jacob.org/sit-eos-voluptas-commodi.html"
              "expires" => null
              "content_id" => 1
              "content_type" => "App\Image"
              "created_at" => "2019-02-18 09:47:53"
              "updated_at" => "2019-02-18 09:47:53"
            ]
            #original: array:11 [
              "id" => 1
              "title" => "{"en":"CURTSEYING as you're falling.","de":"Teil der Zeit, um zu."}"
              "description" => "{"en":"Alice could think of any that do,' Alice said very politely, feeling quite pleased to find that.","de":"Amte steht, und sagte zu ihm: 'feiner junger Herr! Lieben ist menschlich, nur m\u00fc\u00dft Ihr menschlich."}"
              "author" => "Elsbeth Jakob"
              "copyright" => "Herr Prof. Dr. Stefan Lenz B.Eng."
              "ticket_link" => "http://jacob.org/sit-eos-voluptas-commodi.html"
              "expires" => null
              "content_id" => 1
              "content_type" => "App\Image"
              "created_at" => "2019-02-18 09:47:53"
              "updated_at" => "2019-02-18 09:47:53"
            ]
            #changes: []
            #casts: []
            #dates: []
            #dateFormat: null
            #appends: []
            #dispatchesEvents: []
            #observables: []
            #relations: []
            +timestamps: true
            #hidden: []
            #visible: []
            #guarded: array:1 [
              0 => "*"
            ]
          }
        ]
        +timestamps: true
        #hidden: []
        #visible: []
        #guarded: array:1 [
          0 => "*"
        ]
      }
      +with: []
      +additional: []
    }
click's avatar

How did you define 'thumbnail'? Is it a getThumbnailAttribute() or is it a relationship like: thumbnail(). I guess it is the first one, an actual attribute. This is not automatically turned into an array I'm afraid.

You could try two things.

  1. Turn your thumbnail attribute into a relationship.
  2. Try adding a record to the casts property within your model like this: (more info about casting: https://laravel.com/docs/5.5/eloquent-mutators#attribute-casting)
protected $casts = [
   'thumbnail' => 'array',
];
unlikenesses's avatar

@CLICK - Thanks for the advice, though I'm afraid I'm not sure what you mean. This is my CarResource:

public function toArray($request)
{
        return [
            'id' => $this->id,
            'title' => $this->title,
            'subtitle' => $this->subtitle,
            'thumbnail' => $this->image_id ? new ImageResource($this->image) : null
        ];
}

And this is the relevant part of my Car model:

public function image()
{
        return $this->belongsTo('App\Image');
}

So the thumbnail attribute in my CarResource is neither an attribute or a relationship, but another resource.

unlikenesses's avatar

In the end I fixed it by adding jsonSerialize() to the sub-resource. E.g.:

public function toArray($request)
{
        return [
            'id' => $this->id,
            'title' => $this->title,
            'subtitle' => $this->subtitle,
            'thumbnail' => $this->image_id ? (new ImageResource($this->image))->jsonSerialize() : null
        ];
}

This becomes a bit of a pain when you have multiple nested resources, and looks pretty ugly, but it works.

hondnl's avatar

Sometimes I find the Laravel API resources a bit too much for what you need. The overhead alone and the amount of extra files you have to write.

You can easily write your own ... and use your imagination to create a class that could do this automatically. Like has relationship ? > fetch resource

$image_resource =function($image){
                        return [
                          thumbnail_path => $image->path
                        ]
                    });

$resource =function($item) use ($image_resource){


        return [
            'id' => $item->id,
            'title' => $item->title,
            'subtitle' => $item->subtitle,
            'thumbnail' => $item->image_id ? $item->image->map($image_resource)->all() :null;
        ];

});

$rows = Car:all();

return $rows->map($resource)->all();

MN-HettigerM's avatar

I've found another solution that seems to be working pretty well even with resource collections / pagination / wrapping:

    public function show(string $event, Request $request)
    {
        return JsonResponse::fromJsonString(Cache::remember(
            'event_v1_' . hash('sha256', $event),
            now()->addHour(),
            fn() => EventResource::make(Event::findOrFail($event))
                ->toResponse($request)
                ->getContent()
        ));
    }
2 likes
oktay's avatar

@MN-HettigerM you're awesome man, thanks for solution

1 like

Please or to participate in this conversation.