Public/private function duplication
I’m often finding myself writing two versions of the same function: a public one with a user guard (using convex-helpers custom functions) and an internal one where i pass the user identity in using the arguments. i’ve just been extracting out the handler function so i can use it in both functions, but it’s a lot of boilerplate code and it’s sometimes annoying to have to refactor a bunch of public functions after i realize i also want to call them internally. i was wondering if anyone else has this experience or has any suggestions for a nice way to deal with it?
4 Replies
I felt this a lot until I split my backend into database and api layers, meaning I have a set of files that only handle direct database interactions, and are the only place where
ctx.db
is used. The rest of my backend formed the api, both public and private. With this setup, most of the reused parts between public and private functions are already split off into that db layer. For me, this has more or less eliminated the dynamic you're describing.In a similar vein, when I've written my own Convex apps, I'll put almost all of my logic in functions like
function foo(ctx: QueryCtx, args)
(or MutationCtx
/ ActionCtx
based on where this function can be used). And then have a very thin layer defining my API (public and internal) with the query
/ mutation
/ action
wrappers that basically just does argument validation and then calls into one or two of the existing helper functions.
This is kind of just proactively doing the refactor to pull out of your logic into a helper
For the user identity thing in particular, I might try having two wrappers like publicLoggedInQuery
and internalQueryWithUser
that both add user
to ctx
(but the former reading from ctx.auth
and the latter reading from arguments) and then making most of my helper functions take in ctx: QueryCtx & { user: User }
Cool, so it sounds like I should be a bit more proactive about organizing my code. my backend codebase isn't too big yet so it shouldn't be too bad to rearrange some things
btw there's a type utility in
customFunctions
called CustomCtx
that you can use like CustomCtx<typeof publicLoggedInQuery>
for the ctx argument. You can define that once after you define the custom query, so you can just use something like ctx: LoggedInCtx
in your helper functions.