Queries
Queries are the bread and butter of your backend API. They fetch data from thedatabase, check authentication or perform other business logic, and return databack to the client application.
This is an example query, taking in named arguments, reading data from thedatabase and returning a result:
import{ query}from"./_generated/server";
import{ v}from"convex/values";
// Return the last 100 tasks in a given task list.
exportconst getTaskList=query({
args:{ taskListId: v.id("taskLists")},
handler:async(ctx, args)=>{
const tasks=await ctx.db
.query("tasks")
.filter((q)=> q.eq(q.field("taskListId"), args.taskListId))
.order("desc")
.take(100);
return tasks;
},
});
Read on to understand how to build queries yourself.
Query names
Queries are defined in
convex/directory.The path and name of the file, as well as the way the function is exported fromthe file, determine the name the client will use to call it:
// This function will be referred to as `api.myFunctions.myQuery`.
exportconst myQuery= …;
// This function will be referred to as `api.myFunctions.sum`.
exportconst sum= …;
To structure your API you can nest directories inside theconvex/ directory:
// This function will be referred to as `api.foo.myQueries.listMessages`.
exportconst listMessages= …;
Default exports receive the namedefault.
// This function will be referred to as `api.myFunctions.default`.
exportdefault …;
The same rules apply tomutations andactions, whileHTTP actions use a different routing approach.
Client libraries in languages other than JavaScript and TypeScript use stringsinstead of API objects:
api.myFunctions.myQueryis"myFunctions:myQuery"api.foo.myQueries.myQueryis"foo/myQueries:myQuery".api.myFunction.defaultis"myFunction:default"or"myFunction".
Thequery constructor
To actually declare a query in Convex you use thequery constructor function.Pass it an object with ahandler function, which returns the query result:
import{ query}from"./_generated/server";
exportconst myConstantString=query({
args:{},
handler:()=>{
return"My never changing string";
},
});
Query arguments
Queries accept named arguments. The argument values are accessible as fields ofthe second parameter of the handler function:
import{ query}from"./_generated/server";
exportconst sum=query({
handler:(_, args:{ a:number; b:number})=>{
return args.a+ args.b;
},
});
Arguments and responses are automatically serialized and deserialized, and youcan pass and return most value-like JavaScript data to and from your query.
To both declare the types of arguments and to validate them, add anargsobject usingv validators:
import{ query}from"./_generated/server";
import{ v}from"convex/values";
exportconst sum=query({
args:{ a: v.number(), b: v.number()},
handler:(_, args)=>{
return args.a+ args.b;
},
});
Seeargument validation for the full list ofsupported types and validators.
The first parameter of the handler function contains the query context.
Query responses
Queries can return values of any supportedConvex type which will be automatically serializedand deserialized.
Queries can also returnundefined, which is not a valid Convex value. When aquery returnsundefinedit is translated tonull on the client.
Query context
Thequery constructor enables fetching data, and other Convex features bypassing aQueryCtx object to the handlerfunction as the first parameter:
import{ query}from"./_generated/server";
import{ v}from"convex/values";
exportconst myQuery=query({
args:{ a: v.number(), b: v.number()},
handler:(ctx, args)=>{
// Do something with `ctx`
},
});
Which part of the query context is used depends on what your query needs to do:
To fetch from the database use the
dbfield. Note that we make the handlerfunction anasyncfunction so we canawaitthe promise returned bydb.get():convex/myFunctions.tsTSimport{ query}from"./_generated/server";
import{ v}from"convex/values";
exportconst getTask=query({
args:{ id: v.id("tasks")},
handler:async(ctx, args)=>{
returnawait ctx.db.get(args.id);
},
});Read more aboutReading Data.
To return URLs to stored files use the
storagefield. Read more aboutFile Storage.To check user authentication use the
authfield. Read more aboutAuthentication.
Splitting up query code via helpers
When you want to split up the code in your query or reuse logic across multipleConvex functions you can define and call helper
import{ Id}from"./_generated/dataModel";
import{ query, QueryCtx}from"./_generated/server";
import{ v}from"convex/values";
exportconst getTaskAndAuthor=query({
args:{ id: v.id("tasks")},
handler:async(ctx, args)=>{
const task=await ctx.db.get(args.id);
if(task===null){
returnnull;
}
return{ task, author:awaitgetUserName(ctx, task.authorId??null)};
},
});
asyncfunctiongetUserName(ctx: QueryCtx, userId: Id<"users">|null){
if(userId===null){
returnnull;
}
return(await ctx.db.get(userId))?.name;
}
You canexport helpers to use them across multiple files. They will not becallable from outside of your Convex functions.
SeeType annotating server side helpersfor more guidance on TypeScript types.
Using NPM packages
Queries can import NPM packages installed innode_modules. Not all NPMpackages are supported, seeRuntimes for more details.
npm install @faker-js/faker
import{ query}from"./_generated/server";
import{ faker}from"@faker-js/faker";
exportconst randomName=query({
args:{},
handler:()=>{
faker.seed();
return faker.person.fullName();
},
});
Calling queries from clients
To call a query fromReact use theuseQuery hook along with the generatedapi object.
import{ useQuery}from"convex/react";
import{ api}from"../convex/_generated/api";
exportfunctionMyApp(){
const data=useQuery(api.myFunctions.sum,{ a:1, b:2});
// do something with `data`
}
See theReact client documentation for all the ways queriescan be called.
Caching & reactivity & consistency
Queries have three awesome attributes:
- Caching: Convex caches query results automatically. If many clientsrequest the same query, with the same arguments, they will receive a cachedresponse.
- Reactivity: clients can subscribe to queries to receive new results whenthe underlying data changes.
- Consistency: All database reads inside a single query call are performedat the same logical timestamp. Concurrent writes do not affect the queryresults.
To have these attributes the handler function must bedeterministic, whichmeans that given the same arguments (including the query context) it will returnthe same response.
For this reason queries cannotfetch from third party APIs. To call thirdparty APIs, useactions.
You might wonder whether you can use non-deterministic language functionalitylikeMath.random() orDate.now(). The short answer is that Convex takes careof implementing these in a way that you don't have to think about thedeterministic constraint.
SeeRuntimes for more detailson the Convex runtime.
Limits
Queries have a limit to the amount of data they can read at once to guaranteegood performance. Check out these limits inRead/write limit errors.
For information on other limits, seeLimits.