Cache Supabase response at the Edge with KV storage

Jon Meyers
InstructorJon Meyers
Share this video with your friends

Social Share Links

Send Tweet

KV Storage allows us to cache a value as close as possible to our Cloudflare Worker. The first time a user navigates to a route, we don't have the data in the cache, so we need to make a request to the Supabase origin server.

This server could be on the other side of the world and take a long time to respond. Therefore, if we cache the response we get back for a particular route, there is no need to make an additional request to the origin.

In this lesson, we implement cache-aware routes for fetching many articles or a single article. This first checks whether we have the cached value, and sends it back immediately if we do. If not, it will make a request for fresh data from Supabase, and cache the response for the next visitor.

Additionally, we simulate what a slow request might be like, as we might not experience this with super fast computers geographically close to our hosting servers.

Code Snippets

Cache-aware many articles route

router.get(
  "/articles",
  async (request, { SUPABASE_URL, SUPABASE_ANON_KEY, ARTICLES }) => {
    const cachedArticles = await readFrom(ARTICLES, "/articles");

    if (cachedArticles) {
      console.log("sending the cache");
      return json(cachedArticles);
    }

    console.log("fetching fresh articles");

    const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);

    const { data } = await supabase.from("articles").select("*");
    await writeTo(ARTICLES, "/articles", data);
    return json(data);
  }
);

Cache-aware one article route

router.get(
  "/articles/:id",
  async (request, { SUPABASE_URL, SUPABASE_ANON_KEY, ARTICLES }) => {
    const { id } = request.params;
    const cachedArticle = await readFrom(ARTICLES, `/articles/${id}`);

    if (cachedArticle) {
      console.log("sending the cache");
      return json(cachedArticle);
    }

    console.log("fetching fresh article");

    const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);

    const { data } = await supabase
      .from("articles")
      .select("*")
      .match({ id })
      .single();

    await writeTo(ARTICLES, `/articles/${id}`, data);

    return json(data);
  }
);

Simulate 3 second delay

const data = await new Promise((resolve) => {
  setTimeout(async () => {
    const { data } = await supabase.from("articles").select("*");
    resolve(data);
  }, 3000);
});

Delete key from KV Store

npx wrangler kv:key delete --binding=ARTICLES '/articles' --preview

Run wrangler development server

npx wrangler dev

Resources

Jon Meyers: [0:00] The first time we navigate to this article's route, we don't have anything in our KV store. We don't know which articles we want to send back to the user. We have to make a request to that origin server to Supabase to go and get those. [0:11] Once we've fetched these results, we could write them to the cache and then the next user doesn't need to make an additional request all the way to the origin server. They can just read it directly from the KV store.

[0:21] In our /articles route, we need to pull out our KV store that's being passed into this handler. Then once we get this data back from Supabase, we can write it to our cache. Again, that's at /articles. We want to keep this path or key to our KV store in sync with the path for our route. That way we know this is the data that should be returned from this specific route.

[0:45] Now we need to add the data that we want to write to the cache. If we save this file and head back over to the browser and refresh, you can see that we're getting those results pretty quickly from Supabase and then writing them to the cache. If we navigate to our read-kv route, we can see that our KV store has been populated with those results. If we refresh, it's super, super fast getting them from our KV store.

[1:07] Currently, our Supabase database is fairly close to us geographically. It doesn't take that much time to go and get these fresh results. We can simulate what this might be like for someone on the other side of the world by using this code snippet to essentially add a three-second delay.

[1:21] If we then move our request to Supabase up into our setTimeout, we're then going to wait for this Promise to resolve, which will happen after three seconds, we'll pass back the data and that's what we'll get back here and then write to our cache and send back as a response. Exactly the same logic, but just delayed by three seconds.

[1:39] If we now save this and head back to our browser and refresh, we can see that this is taking significantly longer. Actually, around about three seconds. If we navigate to our read-kv route, we're still getting that almost instantaneously. If we already have these results in our KV store, there's no need going off to Supabase to fetch those fresh results. We can just return them directly from the cache.

[2:00] Let's start by first checking whether we have cached articles. We can do this by awaiting a readFrom our ARTICLES cache under the path /articles. If we have cachedArticles, then we just want to return these directly. If we don't have a version already cached, we can go and get them from Supabase.

[2:20] We can test this is working by quitting our development server and type npx wrangler kv:key. Then we want to delete a particular key. We need to tell it which KV store by saying --binding, and so this is going to be ARTICLES, all uppercase. Then the key that we want to delete is /articles. We also need to append the --preview flag.

[2:42] Now, when we run that, it will delete our /articles key. If we run npx wrangler dev and come back over to the browser and refresh, we'll see that we need to wait that three seconds for that first request to go after Supabase, but then every other time we refresh, that's coming straight back from our KV store.

[2:59] Let's remove that three-second delay from our request to Supabase. Instead, we can just console.log. If we already have a cache value, then we can say we're sending the cache and otherwise that we are fetching fresh articles.

[3:14] We can follow a similar pattern for our dynamic route. We need to destructure our ARTICLES KV store, and then we'll replace the body of our function. If we already have a cachedArticle by reading from our ARTICLES KV store at the path for our particular id then we'll send back that cachedArticle, but otherwise we'll go and fetch a fresh article from Supabase.

[3:37] Once we get that response back, we'll go and write it to our ARTICLES KV store for this particular id and then send it back as the response.

[3:46] Now, if we head back to the browser and copy one of these IDs and go to /articles/ this id, we'll see the details for that particular article. If we have a look in the console, we fetched this article fresh from Supabase. If we refresh a couple of times, we'll see that that's coming back very, very quickly. If we go and have a look in the console, we're sending it from the cache.