Skip to content
This repository has been archived by the owner on Mar 20, 2024. It is now read-only.

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

Open
ViVa98 opened this issue May 7, 2021 · 9 comments

Comments

@ViVa98
Copy link

ViVa98 commented May 7, 2021

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.

@patrixr
Copy link
Owner

patrixr commented May 7, 2021

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 strapi-middleware-cache@2.0.1-beta.2

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 !

@ViVa98
Copy link
Author

ViVa98 commented May 7, 2021

@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.

@stafyniaksacha
Copy link
Collaborator

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);
        }
      }
    },
  },
};

@ViVa98
Copy link
Author

ViVa98 commented May 8, 2021

[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']

@stafyniaksacha
Copy link
Collaborator

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

@ViVa98
Copy link
Author

ViVa98 commented May 10, 2021

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
Copy link
Author

ViVa98 commented May 10, 2021

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.

@stafyniaksacha
Copy link
Collaborator

stafyniaksacha commented May 11, 2021

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: {
      // ...
    },
  },
});

@ViVa98
Copy link
Author

ViVa98 commented May 11, 2021

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":[]
            }
         ]
      }
   ]
}

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants