Karlstens
Karlstens•17mo ago

Passing Arguments

Hey all, finally found more time to learn Convex. I'm stuck on passing an argument, where for example, I have a table full of email addresses that acts as a whitelist - and I'd like my server to pass an email string to the function, Convex check the email does/doesn't exist, and return true/false. Here's my server app and my functions;
//server.mjs
import { ConvexHttpClient } from "convex/browser";
import { api } from "./convex/_generated/api.js";
import * as dotenv from "dotenv";
dotenv.config({ path: ".env.local" });

const client = new ConvexHttpClient(process.env["CONVEX_URL"]);

client.query(api.whitelistFunctions.get).then(console.log);

client
.query(api.whitelistFunctions.checkEmail, { email: "sample@email.com" })
.then(console.log); // This will print true if the email exists, otherwise false
//server.mjs
import { ConvexHttpClient } from "convex/browser";
import { api } from "./convex/_generated/api.js";
import * as dotenv from "dotenv";
dotenv.config({ path: ".env.local" });

const client = new ConvexHttpClient(process.env["CONVEX_URL"]);

client.query(api.whitelistFunctions.get).then(console.log);

client
.query(api.whitelistFunctions.checkEmail, { email: "sample@email.com" })
.then(console.log); // This will print true if the email exists, otherwise false
And my whitelistFunctions.js
//whitelistFunctions.js
import { query } from "./_generated/server";
import { v } from "convex/values";

export const get = query({
args: {},
handler: async (ctx) => {
return await ctx.db.query("whitelist").collect();
},
});

export const checkEmail = query({
args: { email: v.string() },

handler: async (ctx) => {
if (!ctx.args.email) {
throw new Error("Email argument is missing or undefined");
}
return await ctx.args.email;
},
});
//whitelistFunctions.js
import { query } from "./_generated/server";
import { v } from "convex/values";

export const get = query({
args: {},
handler: async (ctx) => {
return await ctx.db.query("whitelist").collect();
},
});

export const checkEmail = query({
args: { email: v.string() },

handler: async (ctx) => {
if (!ctx.args.email) {
throw new Error("Email argument is missing or undefined");
}
return await ctx.args.email;
},
});
Get the error
Error: Uncaught TypeError: Cannot read properties of undefined (reading 'email')
at handler (../convex/whitelistFunctions.js:15:13)

at ConvexHttpClient.query (file:///C:/DEV/VS%20Code/Convex/karlstensWebsite/node_modules/convex/dist/esm/browser/http_client.js:122:15)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
Error: Uncaught TypeError: Cannot read properties of undefined (reading 'email')
at handler (../convex/whitelistFunctions.js:15:13)

at ConvexHttpClient.query (file:///C:/DEV/VS%20Code/Convex/karlstensWebsite/node_modules/convex/dist/esm/browser/http_client.js:122:15)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
*I should note that I started to simplify/hack checkEmail right back to the bone, so that essentially I was just returning the passed argument - but still couldn't figure it out.
9 Replies
ballingt
ballingt•17mo ago
Instead of as a property on ctx, args are passed as a second argument to the Convex function
handler: async (ctx, args) => {
if (!args.email) {
handler: async (ctx, args) => {
if (!args.email) {
instead of
handler: async (ctx) => {
if (!ctx.args.email) {
handler: async (ctx) => {
if (!ctx.args.email) {
Karlstens
KarlstensOP•17mo ago
yah, I think I got this sorted....
export const checkEmail = query({
args: { email: v.string() },

handler: async (_, args) => {
if (!args.email) {
throw new Error("Email argument is missing or undefined");
}
return await args.email;
},
});
export const checkEmail = query({
args: { email: v.string() },

handler: async (_, args) => {
if (!args.email) {
throw new Error("Email argument is missing or undefined");
}
return await args.email;
},
});
So now that I have the argument passing - I'll find some time after cooking dinner to write the function to check the email passed in the argument against all emails in the table, and return true of false if it's found. I have a working example done in Airatble, but differently - where I query my Airtable from my server via a CURL containing a formula query. I'm not sure if the same thing can be done in Convex? Or if, I'm just writing the Convex cloud function to do the specific task of finding/matching the given email.
Karlstens
KarlstensOP•17mo ago
For Airtable, you can construct queries with this codepen; https://codepen.io/airtable/full/MeXqOg
ballingt
ballingt•17mo ago
It's more common to write the Convex cloud function to do the specific task of finding/matching an email from a specific table with a specific query. You could include arguments for which table to query or what filters to use, but it's hard to represent an entire query as as argument.
Karlstens
KarlstensOP•17mo ago
Cool, so I'm on the right track 🙂 I wish you could poke chatGPT with all your updated docs - would make learning a bit quicker. 😛 But I say that about everything now. If only OpenAI could cook my dinner too 😦
ballingt
ballingt•17mo ago
You'll likely end up doing something like this: - write a query that returns whether an email is in the whitelist - write an action that takes the argument, calls the query to look for the email, reads the result and then makes a fetch() or use some JavaScript API to send the email. something like
import { action, query } from "./_generated/server";

import postmark from "postmark";
import { v } from "convex/values";

export default action(async (ctx, { email }: { email: string }) => {
const client = new postmark.ServerClient(
"acbdef-asdfa-asdf-adsfasdf"
);

if (!ctx.runQuery(api.whitelistFunctions.emailAllowed, { email: email })) {
throw new Error("that email isn't in the whitelist");
}

const result = await client.sendEmail({
From: "me@gmail.com",
To: email,
Subject: "Hello from Postmark",
HtmlBody: "<strong>Hello</strong> there!",
TextBody: `Hello from Tom!`,
MessageStream: "outbound",
});
console.log("email sent...");
console.log(result);
});

export const emailAllowed = query({
args: { email: v.string() },
handler: async (ctx) => {
const whitelist = ctx.db.query("whitelist").collect();
const emails = whitelist.map((record) => record.email);
return emails.includes(email);
},
});
import { action, query } from "./_generated/server";

import postmark from "postmark";
import { v } from "convex/values";

export default action(async (ctx, { email }: { email: string }) => {
const client = new postmark.ServerClient(
"acbdef-asdfa-asdf-adsfasdf"
);

if (!ctx.runQuery(api.whitelistFunctions.emailAllowed, { email: email })) {
throw new Error("that email isn't in the whitelist");
}

const result = await client.sendEmail({
From: "me@gmail.com",
To: email,
Subject: "Hello from Postmark",
HtmlBody: "<strong>Hello</strong> there!",
TextBody: `Hello from Tom!`,
MessageStream: "outbound",
});
console.log("email sent...");
console.log(result);
});

export const emailAllowed = query({
args: { email: v.string() },
handler: async (ctx) => {
const whitelist = ctx.db.query("whitelist").collect();
const emails = whitelist.map((record) => record.email);
return emails.includes(email);
},
});
(postmark is just an example, one of many client libraries used to send emails)
Karlstens
KarlstensOP•17mo ago
Ok, so I'm starting to understand Convex, and I'm really excited by this. Here's my cloud function;
import { query } from "./_generated/server";
import { v } from "convex/values";

export const checkEmail = query({
args: { email: v.string() },

handler: async (ctx, args) => {
const emailValid = await ctx.db
.query("whitelist")
.filter((q) => q.eq(q.field("Email"), args.email))
.first();
return emailValid;
},
});

//... more functions, MORE POWER! 🙊🙉🙈
import { query } from "./_generated/server";
import { v } from "convex/values";

export const checkEmail = query({
args: { email: v.string() },

handler: async (ctx, args) => {
const emailValid = await ctx.db
.query("whitelist")
.filter((q) => q.eq(q.field("Email"), args.email))
.first();
return emailValid;
},
});

//... more functions, MORE POWER! 🙊🙉🙈
And I set my server with promise.allSettled() server.mjs
import { ConvexHttpClient } from "convex/browser";
import { api } from "./convex/_generated/api.js";
import * as dotenv from "dotenv";
dotenv.config({ path: ".env.local" });

const client = new ConvexHttpClient(process.env["CONVEX_URL"]);

Promise.allSettled([
//client.query(api.whitelistFunctions.get),
// ... more client.queries as needed
client.query(api.whitelistFunctions.checkEmail, {
email: "janesmith@gmail.com",
}),
]).then((results) => {
results.forEach((result) => {
if (result.status === "fulfilled") {
console.log(result.value);
} else {
console.error("Promise was rejected:", result.reason);
}
});
});
import { ConvexHttpClient } from "convex/browser";
import { api } from "./convex/_generated/api.js";
import * as dotenv from "dotenv";
dotenv.config({ path: ".env.local" });

const client = new ConvexHttpClient(process.env["CONVEX_URL"]);

Promise.allSettled([
//client.query(api.whitelistFunctions.get),
// ... more client.queries as needed
client.query(api.whitelistFunctions.checkEmail, {
email: "janesmith@gmail.com",
}),
]).then((results) => {
results.forEach((result) => {
if (result.status === "fulfilled") {
console.log(result.value);
} else {
console.error("Promise was rejected:", result.reason);
}
});
});
And it... just works! I'm really sold on Convex, I can't thank you enough Tom. One last question, to check against multiple fields, is it typical to stack filters() as such? Technically it works, but I do like to check my methods...
export const checkEmailValid = query({
args: { email: v.string() },

handler: async (ctx, args) => {
const emailValid = await ctx.db
.query("whitelist")
.filter((q) => q.eq(q.field("Email"), args.email))
.filter((q) => q.eq(q.field("Enabled"), true))
.first();
return !!emailValid;
},
});
export const checkEmailValid = query({
args: { email: v.string() },

handler: async (ctx, args) => {
const emailValid = await ctx.db
.query("whitelist")
.filter((q) => q.eq(q.field("Email"), args.email))
.filter((q) => q.eq(q.field("Enabled"), true))
.first();
return !!emailValid;
},
});
One side question I have, is that I'm reading about limits - that filter is limited to 16384 documents/records. I'm not fully up-to-scratch on table limits and batch queries, but say for example, I had 20,000 email addresses, and I had to query for an existing email in that 20,000 - how do I build a query function to cater for a large table beyond 16384 documents? I started reading about indexing, but although I could get my Table schema, I wasn't too sure on how to update it with the indexing mechanism. Nor then how to update my query to leverage the index (which... I'm guessing resolves querying a table with more than 16384 documents?) (oh gawd, I just realised, I'm that "one last question..." guy. I'm so sorry!)
Michal Srb
Michal Srb•17mo ago
Yup, Indexes are the answer. https://docs.convex.dev/database/indexes/ Add to schema .index("byEmail", ["email"]) Query:
const users = await ctx.db
.query("users")
.withIndex("byEmail", (q) =>
q.eq("email", someEmail)
)
.unique();
const users = await ctx.db
.query("users")
.withIndex("byEmail", (q) =>
q.eq("email", someEmail)
)
.unique();
Indexes | Convex Developer Hub
Indexes are a data structure that allow you to speed up your
Karlstens
KarlstensOP•17mo ago
Cool - been reading through the docco. Convex is awesome!

Did you find this page helpful?