Next.js Discord

Discord Forum

Cache third party data on api route

Answered
Spectacled bear posted this in #help-forum
Open in Discord
Spectacled bearOP
type Object = {
    //some data
};

const CACHE_DURATION = 60 * 60 * 1000;
let cache: { timestamp: number; data: Object[] } | null = null;

export async function getObjects() {
  const currentTime = Date.now();

  if (cache && currentTime - cache.timestamp < CACHE_DURATION)
    return cache.data;

  const objects = await new Promise<Object[]>((resolve, reject) => {
    https.get("https://example.com/", (res: IncomingMessage) => {
      let data = "";

      res.on("data", (chunk: string) => {
        data += chunk;
      });

      res.on("end", () => {
        const root = parse(data);
        const servers = root.querySelectorAll("tr.server-list__row");

        const objects: Object[] = [];

        servers.forEach((server) => {
          const data = server.querySelector("foo");
          objects.push({
              //data
          });
        });

        objects.sort();

        resolve(objects);
      });

      res.on("error", (error: Error) => {
        reject(
          new TRPCError({
            code: "INTERNAL_SERVER_ERROR",
            message: error.message,
          })
        );
      });
    });
  });

  cache = { timestamp: currentTime, data: worlds };
  return objects;
}

export const someRouter = createTRPCRouter({
  list: protectedProcedure.query(async () => {
      return await getObjects();
  }),
});

This is one of my routers. It fetches data from a third party. The goal is to cache this data so I don't have to fetch it each time my clients send a request. But I am starting to understand that serverless stuff doesn't work that way...

I am using T3 and hosting on vercel.
Does this code make any sense? Or should I use something like redis for this?
Answered by Spectacled bear
ChatGPT 4:
"Given your example, it seems like the fetch function is running on a serverless function on Vercel. In this context, we can't set the next: { revalidate: 600 } in the fetch option as you have tried to do. "
Me: how do I apply to my code?
ChatGPT:
comes up with the in memory cache that I also had
"Keep in mind that with this approach, the cache is kept in memory, which means it will not persist across different function invocations in a serverless environment. You may experience cold starts (when a new serverless function instance is spun up) which will result in the cache being empty and the need to fetch the data again. If this is not desirable and you need a longer-lived, more persistent cache, you should use an external cache like Redis."

Conclusion:
Yes I need to use Redis
View full answer

42 Replies

Spectacled bearOP
async function getObjects(): Promise<Object[]> {
  const res = await fetch("https://example.com", {
      next: {
      revalidate: 600,
    },
  });

  const data = await res.text();

  const root = parse(data);
  const fooList = root.querySelectorAll("bar");

  const objects: Object[] = [];

  fooList.forEach((foo) => {
    
    objects.push({
      ...foo
    });
  });

  return objects;
}

Would this solve the issue? Moreover, does next: {revalidate: 600 } mean the server will only refetch the third party data after 600 seconds since last time?
Does this code make any sense?
as you noticed having a cache as an object in a module makes sense but not for serverless, because the process is killed after a while so the cache isn't very useful
revalidate: 600 means the request will only be fetched at most once every 600 seconds. it will only trigger a new refetch if you try to access the resource after 600 seconds, so it is different of "every 600 seconds" since you could have 1 hour with no fetches if your route doesn't have any traffic
Spectacled bearOP
I dont have an app dir. I use t3.gg
you might want to test if revalidate still works if you are using the pages dir but if it does not, then you will need to rely on an external service to cache the data
Spectacled bearOP
The fetch is in /server/api/routes/
How would i test if that works?
This data is not requested by a browser. This is for an external java program requesting a specific route
create a new test api route with a console.log, change your fetch to fetch your test route, build and run the prod server and check if when making multiple requests you are seeing multiple logs in the console
Spectacled bearOP
I put this in a random api endpoint:
  const res2 = await fetch("https://my.domain.com/", {
    next: {
      revalidate: 600,
    },
  });

I built and started next using npx build next npx start next and checked the apache access log of my.domain.com and it does send a new request each time.
Spectacled bearOP
ChatGPT 4:
"Given your example, it seems like the fetch function is running on a serverless function on Vercel. In this context, we can't set the next: { revalidate: 600 } in the fetch option as you have tried to do. "
Me: how do I apply to my code?
ChatGPT:
comes up with the in memory cache that I also had
"Keep in mind that with this approach, the cache is kept in memory, which means it will not persist across different function invocations in a serverless environment. You may experience cold starts (when a new serverless function instance is spun up) which will result in the cache being empty and the need to fetch the data again. If this is not desirable and you need a longer-lived, more persistent cache, you should use an external cache like Redis."

Conclusion:
Yes I need to use Redis
Answer
Spectacled bearOP
@Rafael Almeida I appreciate the idea to test by calling a server and checking if that is called repeatedly in production. It seems to be the case that the next revalidate does not work in the serverless functions
@Spectacled bear <@258390283127881728> I appreciate the idea to test by calling a server and checking if that is called repeatedly in production. It seems to be the case that the next revalidate does not work in the serverless functions
ah it does work with serverless functions, but it looks like it does not work if you are using the pages dir, only the app dir. so if you can't migrate then yeah you need to use an external service
Spectacled bearOP
I am not sure which dirs you are talking about but theyre not applicable to my project
This endpoint will not be called by a nextjs page, but from a trpc route that is called by an external kotlin program
I'd prefer vercel caching this instead of using redis but I am not sure if thats possible
"app dir" is mostly an alias to the new App Router, see the docs page about the migration: https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration
if you are fetching the external service using fetch from a route handler or a page component then next.revalidate will work with fetch as long as you are using the new router
Spectacled bearOP
I am a bit confused
The code is in server.ts
schedules: publicProcedure.query(async ({ ctx }) => {
//trpc endpoint
const worlds = await getWorlds();
return worlds;
}
you are using the old router (src/pages) so the caching tech is not available
Spectacled bearOP
Damn I thought I was up to date 😦
it only works if you are using the new router (src/app)
Spectacled bearOP
Is migration easy?
ehhh, it depends mostly on the project but I would say no
Spectacled bearOP
Yeah I figured haha
I used create-t3-app
fwiw you can incrementally upgrade to the new router, i.e. you can use both at the same time. so technically you could have only your routes using the new router to take advantage of the new cache stuff
Spectacled bearOP
is the new router the
"use server"
stuff?
but I am not sure how trpc is working with the app router so I can't say it is gonna be easy
it looks like t3 didn't upgrade to the new router yet
ah they have an experimental example here if you are curious: https://github.com/trpc/trpc/tree/main/examples/.experimental/next-app-dir
@Spectacled bear is the new router the "use server" stuff?
yeah this is part of the new router, it is mostly the React Server Components, Server Actions and the new cache mechanisms
Spectacled bearOP
I've learned to stay away from experimental stuff. I'll use redis for rate limiting anyway so for this project it's not horrible
to be clear the old router is not deprecated, I am still using it in a few projects, it just doesn't have the new features
Spectacled bearOP
I appreciate the tips raf!
yeah that's a good solution, you can move to the new router once it is more widely adopted
Spectacled bearOP
Where would I put the redis.ts? I am not familiar with file structures for web dev
I reckon in /server/