Writing Convex Functions
Convex functions are written in JavaScript or TypeScript, and are run on Convex servers. Functions encapsulate the business logic and provide the public API of a Convex application. They can perform complex aggregations, use NPM libraries, and so on.
There are three types of Convex functions - queries, mutations and actions. Queries and mutations are the primary way to interact with Convex, while actions are used in more limited scenarios when non-deterministic code is needed, such as calling third-party services.
Defining Convex Functions
Functions exported from files inside the convex/
folder wrapped in
query
,
mutation
or
action
comprise the public API of a Convex
application. Actions needs to be defined inside convex/actions
folder.
You can invoke a file's default export by passing its file path relative to the
convex/
folder to useQuery
. Be sure to
leave off the extension. Here's an example:
export default query(...);
// Call "folder_name/file_name.js"'s default export.
useQuery("folder_name/file_name");
You can call non-default exports too. Add a colon and the exported name to the end of the path.
export const exportedName = query(...);
// Call `exportedName` within "folder_name/file_name.js"
useQuery("folder_name/file_name:exportedName");
Function Signatures
Since Convex functions run on the server, subscribing to a query or running a mutation requires sending arguments and return values over the network.
Convex automatically handles serialization of arguments and return values.
Convex supports a superset of JSON: See Types and Schemas for
the supported types. In particular, functions, instances of user-defined
classes, and undefined
aren't supported.
When the server runs your Convex functions, it provides an additional first
argument: a QueryCtx
for queries, a
MutationCtx
for mutations or a
ActionCtx
for actions. These context objects
provide capabilities to query the database or
check client authentication with the auth
property. Queries and
mutations can interact with the database directly via the db
property, while
actions can invoke queries and mutations via the query
and mutation
properties respectively.
Convex query functions can't evaluate to undefined
on the client, since
undefined
isn't a valid Convex value and is used on the client
to signal that a query has not yet returned a value. If a Convex function
returns undefined
, the server will translate this value to null
.
// in convex/getUserByEmail.ts a function can return `undefined`...
export default query(async ({ db }, email: string) => {
const user = db
.query("users")
.filter(q => q.eq(email, q.field("email")))
.first();
if (user) {
return user.name;
}
});
// ...but in a React component this value will be translated to null.
const messages: Message[] | undefined | null = useQuery("getUserByEmail");
Environment
Actions run in Node.js and can use any compatible libraries. Query and
mutations, on the other hand, execute in a more limited environment. They have
have access to fewer built-in APIs. Nearly all
JavaScript global objects
are available. But web APIs like fetch
and window
are absent, as are Node.js
APIs like process
and fs
.
The implementation of some objects differs from stock V8, the JavaScript engine Convex uses to execute these functions, in order to run these functions deterministically.
Using Libraries
When you push Convex functions with
npx convex dev
or
npx convex deploy
, the CLI
tool bundles your functions to include any libraries they may use. Many
libraries just work, but if a library depends on Node.js, it probably won't in
query and mutation functions. Libraries that depends on browser-specific
capabilities won't work in any type of Convex functions.
If there's a library you'd like to use that isn't working for you, let us know!
You can define functions in up to 10,000 files, which once bundled with
dependencies, need to be less than 32MB. If you're running into this size limit,
you can use npx convex deploy -v
to print out each source file's bundled size.
Sharing Code with Your Frontend
Types and functions from your UI code can be imported and used by Convex functions.
import { FeedEntry, formatFeedEntry } from "../src/feedTypes.ts"
export default query(async ({ db }) => {
const entries: FeedEntry[] = [];
...
const formatted = formatFeedEntry(entry)
...
});
Importing modules from your UI could lead you to inadvertently include code that
can't run in the Convex environment. If you encounter type
errors like Cannot find name 'window'
or
Cannot redeclare block-scoped variable 'console'.
you may need to refactor
your UI code to pull code you'd like to share out into separate files that can
be imported by both UI and Convex function code.
Avoid importing Convex functions from UI code
Backend code in Convex functions might contain code you'd like to keep private. Importing Convex functions in UI code may cause your bundler to include this code in the frontend bundle, potentially exposing secret logic to users.