strapi-middleware-cache icon indicating copy to clipboard operation
strapi-middleware-cache copied to clipboard

Bust cache of specific endpoint when PUT/DELETE request with other endpoint

Open ViVa98 opened this issue 3 years ago • 9 comments

I have a model with 3 fields containing {id(auto-generated), username, email}

I have modified the default GET endpoint to "username" instead of "id" to search specific record based on username like GET /profiles/someusername

But for PUT/DELETE I'm using "id" as the endpoint PUT/DELETE /profiles/random_generated_id

Whenever I use PUT/DELETE, data changes in the database, the previously cached data of different endpoint is not busting. I'm thinking of extending those PUT/DELETE API and manually deleting the specific endpoint's cache.

Help me get out of this situation.

ViVa98 avatar May 07 '21 05:05 ViVa98

Hi @ViVa98

The version of the middleware that you are using does not support anything like that, however @stafyniaksacha has been working on a v2 (you can check out the Beta branch, which contains a great amount of new features, including the ability to set custom routes, which might help you fix your issue !

You can try installing it by using npm install --save [email protected]

However note that this version's configuration is not backwards compatible with v1, so do take a look at the documentation on that branch to learn how to configure it.

Hope this helps !

patrixr avatar May 07 '21 05:05 patrixr

@patrixr thanks for the reply and info about the beta version. There is an option to specify a particular API endpoint to bust when a PUT/DELETE request comes for the same endpoint.

What I'm looking for is to bust a specific endpoint cache when PUT/DELETE request for another endpoint.

Requesting @stafyniaksacha to please go through the above-mentioned use case and also to provide a brief/one-line description for the beta version.

ViVa98 avatar May 07 '21 12:05 ViVa98

Hello @ViVa98 You can use the internal cache middleware by setting the withStrapiMiddleware to true and use it in lifecycle methods

Let's say you have this cache config:

// file: config/cache.js

/**
 * @type {import('strapi-middleware-cache').UserMiddlewareCacheConfig}
 */
module.exports = {
  enabled: true,
  clearRelatedCache: true,
  withStrapiMiddleware: true,
  models: [
    {
      model: "profile",
      injectDefaultRoutes: false,
      routes: [
        "/profiles/:slug",
      ],
    },
  ],
};

this will register only /profiles/:slug route to be cached, you have to clear it manually then with lifecycles:

// file: api/profile/models/profile.js

/**
 * Lifecycle callbacks for the `profile` model.
 */
async function clearProfileCache(data) {
  const cache = strapi?.middleware?.cache || {};

  if (cache && typeof cache.clearCache === "function") {
    const profileCache = cache.getCacheConfig("profile");

    if (profileCache && typeof data.slug === "string") {
      await cache.clearCache(profileCache, { slug: data.slug });
      return;
    }
  }
}

module.exports = {
  lifecycles: {
    async afterDelete(result, data) {
      try {
        await clearProfileCache(result);
      } catch (error) {
        strapi.log.error("profile afterDelete:clearProfileCache");
        strapi.log.error(error);

        if (
          typeof strapi.plugins?.sentry?.services?.sentry?.sendError ===
          "function"
        ) {
          strapi.plugins.sentry.services.sentry.sendError(error);
        }
      }
    },

    async afterUpdate(result, params, data) {
      try {
        await clearProfileCache(result);
      } catch (error) {
        strapi.log.error("profile afterUpdate:clearProfileCache");
        strapi.log.error(error);

        if (
          typeof strapi.plugins?.sentry?.services?.sentry?.sendError ===
          "function"
        ) {
          strapi.plugins.sentry.services.sentry.sendError(error);
        }
      }
    },
  },
};

stafyniaksacha avatar May 07 '21 17:05 stafyniaksacha

[cache] GET /shops/shop_name **MISS**
GET /shops/shop_name (358 ms) 200

PUT /shops/id (867 ms) 200

[cache] GET /shops/shop_name **HIT**
GET /shops/shop_name (3 ms) 200
[cache] GET /profiles/username **MISS**
GET /profiles/username (280 ms) 200

PUT /profiles/id (1023 ms) 200

[cache] GET /profiles/username **MISS**
GET /profiles/username (324 ms) 200

And also for the shops when logged shopCache the paramNames array is empty [] instead of ['shop_name']. But for profiles, paramNames array is ['username']

ViVa98 avatar May 08 '21 13:05 ViVa98

Sorry, I don't understand what you are expecting. Can you provide more information of what you need and your current configuration?

paramNames are populated from cache config (and not gathered from strapi), on the example I sent there is only one route /profiles/:slug registered with a slug param on profile collection

stafyniaksacha avatar May 08 '21 18:05 stafyniaksacha

paramNames are populated from cache config (and not gathered from strapi), on the example I sent there is only one route /profiles/:slug registered with a slug param on profile collection

You're right about populating paramNames from the cache config but in my case, I configured two models with a route in each one.

// file: config/middleware.js

module.exports = ({ env }) => ({
  settings: {
    cache: {
      enabled: true,
      clearRelatedCache: true,
      withStrapiMiddleware: true,
      models: [
        {
          model: "profile",
          injectDefaultRoutes: false,
          routes: ["/profiles/:username"],
        },
        {
          model: "shop",
          injectDefaultRoutes: false,
          routes: ["/shops/:shop_name"],
        },
      ],
    },
  },
});

After the below line in file: api/shop/models/shop.js const shopCache = cache.getCacheConfig("shop"); I tried logging shopCache and the result is

{
  singleType: false,
  hitpass: [Function: hitpass],
  injectDefaultRoutes: false,
  headers: [],
  maxAge: 3600000,
  model: 'shop',
  routes: [ { path: '/shops/:shop_name', method: 'GET', paramNames: [] } ]
}

If you observe paramNames above, it is empty. It has to be filled like ['shop_name'].

ViVa98 avatar May 10 '21 11:05 ViVa98

For some reason, paramNames is not populating for my second model configured in file: config/middleware.js. Tried hardcoding the shopCache variable like below instead of assigning from cache.getCacheConfig("shop")

const shopCache = {
  singleType: false,
  hitpass: [Function: hitpass],
  injectDefaultRoutes: false,
  headers: [],
  maxAge: 3600000,
  model: 'shop',
  routes: [ { path: '/shops/:shop_name', method: 'GET', paramNames: ["shop_name"] } ]
}

After this modification cache is busting when PUT/DELETE request is completed. @stafyniaksacha thank you.

ViVa98 avatar May 10 '21 12:05 ViVa98

Hum, this is wired.

The paramNames should be resolved here (with /:([^/]+)/g regex) And the model entry should be shop in this case (not link)

Can you log the cache.options In your file: api/shop/models/shop.js?
So we can check the resolved configuration.

Also, we will have more information using debug log level:

// file: config/middleware.js

module.exports = ({ env }) => ({
  settings: {
    logger: {
      level: "debug",
      exposeInContext: true,
    },
    cache: {
      // ...
    },
  },
});

stafyniaksacha avatar May 11 '21 02:05 stafyniaksacha

Response from the cache.options using basic console.log

{
  type: 'mem',
  logs: true,
  enabled: true,
  populateContext: false,
  populateStrapiMiddleware: false,
  enableEtagSupport: false,
  enableXCacheHeaders: false,
  clearRelatedCache: true,
  withKoaContext: false,
  withStrapiMiddleware: true,
  headers: [],
  max: 500,
  maxAge: 3600000,
  cacheTimeout: 500,
  models: [
    {
      singleType: false,
      hitpass: [Function: hitpass],
      injectDefaultRoutes: false,
      headers: [],
      maxAge: 3600000,
      model: 'profile',
      routes: [Array]
    },
    {
      singleType: false,
      hitpass: [Function: hitpass],
      injectDefaultRoutes: false,
      headers: [],
      maxAge: 3600000,
      model: 'shop',
      routes: [Array]
    }
  ]
}

Response using strapi.log.debug after adding logger to middleware file

{
   "type":"mem",
   "logs":true,
   "enabled":true,
   "populateContext":false,
   "populateStrapiMiddleware":false,
   "enableEtagSupport":false,
   "enableXCacheHeaders":false,
   "clearRelatedCache":true,
   "withKoaContext":false,
   "withStrapiMiddleware":true,
   "headers":[],
   "max":500,
   "maxAge":3600000,
   "cacheTimeout":500,
   "models":[
      {
         "singleType":false,
         "injectDefaultRoutes":false,
         "headers":[],
         "maxAge":3600000,
         "model":"profile",
         "routes":[
            {
               "path":"/profiles/:username",
               "method":"GET",
               "paramNames":["username"]
            }
         ]
      },
      {
         "singleType":false,
         "injectDefaultRoutes":false,
         "headers":[],
         "maxAge":3600000,
         "model":"shop",
         "routes":[
            {
               "path":"/shops/:shop_name",
               "method":"GET",
               "paramNames":[]
            }
         ]
      }
   ]
}

ViVa98 avatar May 11 '21 09:05 ViVa98