Convex Best Practices
Relevant source files
The following files were used as context for generating this wiki page:
Introduction
Convex serves as the unified backend and database layer for the Barekey project, eliminating the need for a separate API server. It manages data persistence, real-time synchronization, and server-side logic through a collection of queries, mutations, and actions. This page outlines the architectural standards and implementation patterns required to maintain a secure, performant, and type-safe backend environment.
The architecture relies on the ConvexReactClient for frontend communication and utilizes Clerk for identity management. Every database interaction is governed by strict schema definitions and index requirements to ensure efficiency at scale. Sources: AGENTS.md:15-28
Function Types and Visibility
Convex functions are categorized by their side effects and visibility. Adhering to these definitions ensures that internal logic remains protected and public APIs are explicitly defined.
Public vs. Internal Functions
- Public API: Functions exported as
query,mutation, oractionare accessible from the frontend client. They must include argument validators. - Internal API: Functions exported as
internalQuery,internalMutation, orinternalActionare private and only accessible from other Convex functions. - Registration: Functions must never be registered through
apiorinternalobjects directly.
Sources: pkg/convex/_generated/server.d.ts:25-70, AGENTS.md:152-160
Execution Contexts
| Type | Purpose | Restrictions |
|---|---|---|
| Query | Deterministic, read-only database access. | Cannot write to DB; must be fast. |
| Mutation | Atomic, deterministic database writes. | Can call ctx.db.insert, patch, replace, and delete. |
| Action | Non-deterministic code (side effects, HTTP). | Cannot use ctx.db directly; must call queries/mutations. |
Sources: pkg/convex/_generated/server.d.ts:98-138, AGENTS.md:162-177
Database Design and Querying
Efficiency in Convex is driven by proper indexing and avoiding in-memory filtering.
Indexing and Performance
- Filter Restriction: The use of
.filter()on queries is prohibited. Developers must define an index inschema.tsand use.withIndex(). - Index Ordering: Index names should list all fields (e.g.,
by_orgId_and_slug). Fields must be queried in the exact order they are defined in the schema. - Uniqueness: Use
.unique()to assert that a query should return exactly one document or null.
Sources: AGENTS.md:162-166, pkg/convex/schema.ts:16-52
The diagram above shows the decision flow for implementing efficient Convex database queries.
Sources: AGENTS.md:162-184
Data Modification Patterns
- Replace:
ctx.db.replaceperforms a full document replacement. - Patch:
ctx.db.patchperforms a shallow merge of fields. - Delete: Queries do not support a native
.delete(). Logic must.collect()IDs and loop through them to callctx.db.delete(row._id).
Sources: AGENTS.md:168-171
Schema and Data Modeling
The schema is defined in pkg/convex/schema.ts using v (convex/values) for validation.
Core Tables
| Table | Key Fields | Purpose |
|---|---|---|
projects | orgId, slug | Manages project metadata and ownership. |
projectStages | projectId, slug | Defines environments (dev, prod). |
projectKeys | projectId, encryptedDek | Stores wrapped Data Encryption Keys (DEK). |
projectVariables | projectId, stageSlug, name | Stores encrypted secrets. |
Sources: pkg/convex/schema.ts:1-60
Identity and Multi-tenancy
Barekey enforces a strict organization-based ownership model. Individual users never own projects; they must belong to an organization (managed via Clerk). Convex functions utilize ctx.auth.getUserIdentity() and custom helpers like requireActiveOrgIdClaims to enforce these boundaries. Sources: AGENTS.md:38-46, pkg/convex/projects.ts:91-105
Encryption Best Practices
The project uses envelope encryption to secure sensitive environment variables.
Key Management Lifecycle
- Master KEK: Stored as an environment variable (
BAREKEY_MASTER_KEY_B64). - Project DEK: A unique key generated per project, stored in
projectKeysencrypted by the KEK. - Secret Value: Encrypted with the Project DEK and stored in
projectVariables.
Sources: AGENTS.md:50-55, pkg/convex/lib/encryption.ts:114-140
This sequence illustrates the stateless envelope encryption flow used for every secret access request.
Sources: pkg/convex/lib/encryption.ts:114-175
Implementation Constraints
- TypeScript: Use
Id<"tableName">for document IDs instead of plain strings. Sources: AGENTS.md:139 - Circular Dependencies: When calling functions in the same file via
ctx.runQuery, provide explicit return type annotations to prevent TypeScript circularity. Sources: AGENTS.md:158-160 - Node.js Actions: Use
"use node";only when Node.js built-ins are required. This directive must never be in the same file as queries or mutations. Sources: AGENTS.md:173-177 - Validators: Use
v.int64()for large integers;v.bigint()is deprecated. Sources: AGENTS.md:188
Conclusion
Adhering to these Convex best practices ensures that the Barekey backend remains highly secure through envelope encryption, performant through strict indexing, and maintainable through type-safe generated code. Developers should prioritize using indexes over manual filtering and ensure that all data access is scoped to the active organization.