Khalil
Khalil9mo ago

Best practices for writing DB queries/mutations

I am currently migrating my backend from trpc + prisma to Convex and trying to understand the best practices in terms of writing secure and scalable software with the platform. My understanding is that every mutation/query/action/http endpoints that I write is exposed to the world, and it's my job to write the logic inside the handler to prevent actions from malicious users? I currently have a mutation like so:
export const remove = mutation({
args: {
orgId: v.string(),
},
handler: async (ctx, args) => {
await ctx.db.delete(resource._id);
return null;
},
});
export const remove = mutation({
args: {
orgId: v.string(),
},
handler: async (ctx, args) => {
await ctx.db.delete(resource._id);
return null;
},
});
I want to use this mutation both inside an HTTP function that is triggered via webhooks from Clerk, and also inside the webapp for authenticated users. What would be the best way to secure such mutations that are shared between webhooks and user sessions?
5 Replies
jamwt
jamwt9mo ago
lee
lee9mo ago
also you can write javascript functions with common logic that you can call from multiple entry points after checking auth
Khalil
KhalilOP9mo ago
With internal functions it looks like I need to dupe some queries, which I think it's fine generally. I was mostly asking about the best practices for writing a single DB query/mutation that can be consumed by different clients (web app/webhooks/an admin dashboard).
lee
lee9mo ago
i would write the complex query in a javascript function and then call it from all the places. then you don't need to duplicate.
const doComplexStuff = async (ctx, args) => {
const stuff = await ctx.db.query().first();
... etc ...
return result;
};

// to call as webhook
const webhook = httpAction((ctx, request) => {
// authorize
const auth = request.headers.get('Authorization');
// parse args from request
const args = { arg: request.url.path };
const result = doComplexStuff(ctx, args);
}

// to call from React
const webApp = query((ctx, args) => {
// authorize
const auth = ctx.auth;
return doComplexStuff(ctx, args);
}

// to call from Convex dashboard
const fromDashboard = internalQuery((ctx, args) => {
return doComplexStuff(ctx, args);
}
const doComplexStuff = async (ctx, args) => {
const stuff = await ctx.db.query().first();
... etc ...
return result;
};

// to call as webhook
const webhook = httpAction((ctx, request) => {
// authorize
const auth = request.headers.get('Authorization');
// parse args from request
const args = { arg: request.url.path };
const result = doComplexStuff(ctx, args);
}

// to call from React
const webApp = query((ctx, args) => {
// authorize
const auth = ctx.auth;
return doComplexStuff(ctx, args);
}

// to call from Convex dashboard
const fromDashboard = internalQuery((ctx, args) => {
return doComplexStuff(ctx, args);
}
Khalil
KhalilOP9mo ago
I like that pattern, i will try it out, thank you

Did you find this page helpful?