Queries
Queries are the bread and butter of your backend API. They fetch data from the database, check authentication or perform other business logic, and return data back to the client application.
Query names
Queries are defined in files inside your convex/
directory.
The path and name of the file, as well as the way the function is exported from the file, determine the name the client will use to call it.
The simplest name is given to functions default export
ed from a file placed
directly in the convex/
directory:
// This function will be named "myQuery".
export default …;
To structure your API you can nest directories inside the convex/
directory:
// This function will be named "foo/myQuery".
export default …;
Finally it can be handy to declare multiple functions in the same file. This can be done with named exports:
// This function will be named "myFunctions:all".
export const all = …;
// This function will be named "myFunctions:some".
export const some = …;
You can mix and match all of these ways of naming your functions. The same rules apply to mutations and actions, while HTTP endpoints use a different routing approach.
The query
constructor
To actually declare a query in Convex you use the query
constructor function,
and pass it an implementation function returning the result:
import { query } from "./_generated/server";
export default query(() => {
return "My never changing string";
});
Query arguments and responses
Named arguments passed to your query from a client are accessible from the second parameter of the implementation function:
import { query } from "./_generated/server";
export default query((_, { a, b }) => {
return a + b;
});
Arguments are automatically serialized and deserialized, and you can pass and return most value-like JavaScript data to and from your function. See Field Values for the full list of supported types.
The first argument to the implementation function is reserved for the query context.
Query context
The query
constructor enables fetching data, and other Convex features by
passing a QueryCtx object to the
implementation function as the first argument:
import { query } from "./_generated/server";
export default query((queryCtx, { a, b }) => {
// Do something with `queryCtx`
});
Which part of the query context is used depends on what your query needs to do:
-
To fetch from the database use the
db
field. Note that we make the implementation function anasync
function so we canawait
the promise returned bydb.get()
:export default query(async ({ db }, { id }) => {
return await db.get(id);
});Read more about Reading Data.
-
To return URLs to stored files use the
storage
field. Read more about File Storage. -
To check user authentication use the
auth
field. Read more about Authentication.
Calling queries from clients
To call a query from React use the generated
useQuery
hook:
import { useQuery } from "../convex/_generated/react";
export function MyApp() {
const data = useQuery("functionName", { a, b });
…
See Client Libraries for all the ways queries can be called from different clients.
Caching & reactivity
Queries have two awesome attributes:
- Caching: Convex caches query results automatically. If many clients request the same query, with the same arguments, they will receive a cached response.
- Reactivity: clients can subscribe to queries to receive new results when the underlying data changes.
To have these attributes the implementation function must be deterministic, which means that given the same arguments (including the query context) it will return the same response.
For this reason queries cannot call third party APIs. To call third party APIs, use actions.
You might wonder whether you can use non-deterministic language functionality
like Math.random()
or Date.now()
. The short answer is that Convex takes care
of implementing these in a way that you don't have to think about the
deterministic constraint.