You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When you want to pass the result of calling a function around your client codebase, you can use the generated types Doc and Id, just like on the backend:
96
+
97
+
```tsx
98
+
import { Doc, Id } from "../convex/_generated/dataModel";
99
+
100
+
function Channel(props: { channelId: Id<"channels"> }) {
101
+
// ...
102
+
}
103
+
104
+
function MessagesView(props: { message: Doc<"messages"> }) {
105
+
// ...
106
+
}
107
+
```
108
+
109
+
You can also declare custom types inside your backend codebase which include Docs and Ids, and import them in your client-side code.
110
+
111
+
You can also use WithoutSystemFields and any types inferred from validators via Infer.
112
+
113
+
# Best Practices (https://docs.convex.dev/production/best-practices/)
114
+
115
+
## Database
116
+
### Use indexes or paginate all large database queries.
117
+
Database indexes with range expressions allow you to write efficient database queries that only scan a small number of documents in the table. Pagination allows you to quickly display incremental lists of results. If your table could contain more than a few thousand documents, you should consider pagination or an index with a range expression to ensure that your queries stay fast.
118
+
119
+
For more details, check out our Introduction to Indexes and Query Performance article.
120
+
121
+
### Use tables to separate logical object types.
122
+
Even though Convex does support nested documents, it is often better to put separate objects into separate tables and use Ids to create references between them. This will give you more flexibility when loading and querying documents.
123
+
124
+
You can read more about this at Document IDs.
125
+
126
+
## Use helper functions to write shared code.
127
+
Write helper functions in your convex/ directory and use them within your Convex functions. Helpers can be a powerful way to share business logic, authorization code, and more.
128
+
129
+
Helper functions allow sharing code while still executing the entire query or mutation in a single transaction. For actions, sharing code via helper functions instead of using ctx.runAction reduces function calls and resource usage.
130
+
131
+
## Prefer queries and mutations over actions
132
+
You should generally avoid using actions when the same goal can be achieved using queries or mutations. Since actions can have side effects, they can't be automatically retried nor their results cached. Actions should be used in more limited scenarios, such as calling third-party services.
133
+
134
+
## The Zen of Convex (https://docs.convex.dev/zen)
135
+
136
+
### Performance
137
+
Double down on the sync engine
138
+
There's a reason why a deterministic, reactive database is the beating heart of Convex: the more you center your apps around its properties, the better your projects will fare over time. Your projects will be easier to understand and refactor. Your app's performance will stay screaming fast. You won't have any consistency or state management problems.
139
+
140
+
Use a query for nearly every app read
141
+
Queries are the reactive, automatically cacheable, consistent and resilient way to propagate data to your application and its jobs. With very few exceptions, every read operation in your app should happen via a query function.
142
+
143
+
Keep sync engine functions light & fast
144
+
In general, your mutations and queries should be working with less than a few hundred records and should aim to finish in less than 100ms. It's nearly impossible to maintain a snappy, responsive app if your synchronous transactions involve a lot more work than this.
145
+
146
+
Use actions sparingly and incrementally
147
+
Actions are wonderful for batch jobs and/or integrating with outside services. They're very powerful, but they're slower, more expensive, and Convex provides a lot fewer guarantees about their behavior. So never use an action if a query or mutation will get the job done.
148
+
149
+
Don't over-complicate client-side state management
150
+
Convex builds in a ton of its own caching and consistency controls into the app's client library. Rather than reinvent the wheel, let your client-side code take advantage of these built-in performance boosts.
151
+
152
+
Let Convex handle caching & consistency
153
+
Be thoughtful about the return values of mutations
154
+
155
+
### Architecture
156
+
Create server-side frameworks using "just code"
157
+
Convex's built-in primitives are pretty low level! They're just functions. What about authentication frameworks? What about object-relational mappings? Do you need to wait until Convex ships some in-built feature to get those? Nope. In general, you should solve composition and encapsulation problems in your server-side Convex code using the same methods you use for the rest of your TypeScript code bases. After all, this is why Convex is "just code!" Stack always has great examples of ways to tackle these needs.
158
+
159
+
160
+
Don't misuse actions
161
+
Actions are powerful, but it's important to be intentional in how they fit into your app's data flow.
162
+
163
+
Don't invoke actions directly from your app
164
+
In general, it's an anti-pattern to call actions from the browser. Usually, actions are running on some dependent record that should be living in a Convex table. So it's best trigger actions by invoking a mutation that both writes that dependent record and schedules the subsequent action to run in the background.
165
+
166
+
Don't think 'background jobs', think 'workflow'
167
+
When actions are involved, it's useful to write chains of effects and mutations, such as:
168
+
169
+
action code → mutation → more action code → mutation.
170
+
171
+
Then apps or other jobs can follow along with queries.
172
+
173
+
Record progress one step at a time
174
+
While actions could work with thousands of records and call dozens of APIs, it's normally best to do smaller batches of work and/or to perform individual transformations with outside services. Then record your progress with a mutation, of course. Using this pattern makes it easy to debug issues, resume partial jobs, and report incremental progress in your app's UI.
0 commit comments