Where to run a kinda `cron` specific for each user?

I have the necessity to run a function each hour, this function will add some data into different database tables so I do not want to rely on a cron because, if the user stops to use the platform, I'll end up just adding trash to the database for no reason. So my idea is to go into the auth.ts and use the session event provided to go and fetch the lastRun column of my user profile. If the difference between now and lastRun is more than 1 hour, I'll call the function that will do all the things that has to do. Do you think it could be a good approach or there are better ideas out there?
8 Replies
JulieCezar
JulieCezar2y ago
Your idea seems ok... But checking on each session may be a little too much... Because session runs on each page refresh and in some cases een more frequently. Do you really want to slow down the loading of the site each time just to check that? An alternative solution would be to use crons but have another cron checker which will run (e.g once a day) and check if there are any crons that should be deleted and delte them... Now this may be wrong depending on what services you use and how much that would cost you.
cupofcrypto
cupofcryptoOP2y ago
Thanks for the feedback, my 'issue' was about the fact that cron (Vercel ones) are Edge function and can timeout. I know I am solving a problem that I do not have (yet) but the idea of moving on the user session was to make it more fast to check and run the function. Dunno, start to think about putting this code into a localStorage or a cookie and keeping the last time run in there, so the check will be on the client in the same browser 🤔 and I'll write on the database only when the function run and pull the data if the browser does not have that localStorage
bostonsheraff
bostonsheraff2y ago
Maybe you could store the lastRun in an HttpOnly cookie? The check would still be on every page, but at least it saves you a DB query.
cupofcrypto
cupofcryptoOP2y ago
yup really similar to my conclusion, I believe cookies or localStorage have similar performances, but haven't checked it deeply. As I can see by now, I have two options: - move the check to the client - make the cron function as lightweight as possible Still wondering which approach could be the best. I like the client-side approach, but that will also need to account for how many days the user does not log in, adding additional logic. The server-side approach instead will add more pressure on the database side. Need to think about it 🤔
JulieCezar
JulieCezar2y ago
Don't use Vercel Crons, you cannot edit,add,delete them programatically, but have to do it in their dashboard I believe. Instead use something like Upstash which handles crons on the edge 👍 Another question, should this function of yours run each hour or each hour the user is active? Because if it's for all users each hour you can have a cron job that will be get all the users from the db, then do whatever you need to do I see your answer above I like the client-side approach, but that will also need to account for how many days the user does not log in, adding additional logic. But yea, you cannot have it all... you will need to make a compromise somewhere. In case you really need this to be done, you should do it with a cron serverside, because thats the only surefire way And the fact that it will strain your db is not really that important, since it will probably strain more the backend where you do your logic and if you use a db service like planetscale they will scale for you, sou you don't need to worry there
cupofcrypto
cupofcryptoOP2y ago
yup I think I'll do one task each hour for everyone, thanks for the tip on Upstash. Need to dig into it. Also tbh now I am getting some issues on how to run a Next.js API call that will call the tRPC one that will do all the backendy stuff. Don't have much time to explain, kids require attention now, but if you ask "why don't you call the tRPC straigh?' I was getting a 404 even if the procedure is public. Anyway, do you think the gSSP could be a good approach here? Not an expert, found it here and I still have to experiment with it 😅 https://youtu.be/G2ZzmgShHgQ
Christopher Ehrlich
YouTube
Advanced tRPC - Callers, functions, and gSSP
🚨 createSSGHelpers has been renamed to createServerSideHelpers 🚨 Repo for this video: https://github.com/c-ehrlich/you-dont-need-callers If you want to use schema in the frontend, they cannot be imported from the same file as things that run specifically in the backend. One solution would be to put them into a procedureName.schema.ts or simi...
JulieCezar
JulieCezar2y ago
No, for these kind of api calls you need an actual next api route not trpc. So then you would have a cron job that triggers an acutal api endpoint you shouldnt call trpc from an api, instead put the logic in the api endpoint or extract that logic into a function then call the function in 2 places if needed
cupofcrypto
cupofcryptoOP2y ago
I believe I ended up following your suggestion without knowing it 😅 Basically I've created an api/cron/transactions/update in the relative Next.js folder that calls the tRPC endpoint with createCaller in the following way:
import { TRPCError } from "@trpc/server";
import { getHTTPStatusCodeFromError } from "@trpc/server/http";
import type { NextApiRequest, NextApiResponse } from "next";
import { createTRPCContext } from "@/server/api/trpc";
import { appRouter } from "@/server/api/root";

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const caller = appRouter.createCaller(await createTRPCContext({ res, req }));

try {
await caller.transaction.addInterestToAllProjects();
res.status(200).json({ message: "ok" });
} catch (cause) {
if (cause instanceof TRPCError) {
const httpStatusCode = getHTTPStatusCodeFromError(cause);
res.status(httpStatusCode).json({ message: cause.message });

return;
}

res.status(500).json({
error: { message: `Something has gone wrong` },
});
}
}
import { TRPCError } from "@trpc/server";
import { getHTTPStatusCodeFromError } from "@trpc/server/http";
import type { NextApiRequest, NextApiResponse } from "next";
import { createTRPCContext } from "@/server/api/trpc";
import { appRouter } from "@/server/api/root";

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const caller = appRouter.createCaller(await createTRPCContext({ res, req }));

try {
await caller.transaction.addInterestToAllProjects();
res.status(200).json({ message: "ok" });
} catch (cause) {
if (cause instanceof TRPCError) {
const httpStatusCode = getHTTPStatusCodeFromError(cause);
res.status(httpStatusCode).json({ message: cause.message });

return;
}

res.status(500).json({
error: { message: `Something has gone wrong` },
});
}
}
Probably there are better ways to do it, but lucky me it works and right now I have other aspects of the app that I want to finish first. But if you want to leave a different approach I'll make sure to implement it when I'll have time to refactor the code. Also, thank you so much for Upstash, been using it since then and it works like a charm 😅
Want results from more Discord servers?
Add your server