- 数据库
- 后端
【免费下载链接】convex-backend
The open-source reactive database for app developers
导读
Convex 是一个为应用开发者设计的开源响应式数据库与后端平台,函数(Functions)是它的核心编程模型:开发者用 TypeScript 编写查询(Query)、修改(Mutation)与动作(Action),即可获得自动的类型安全、参数校验、实时订阅与数据库读写能力。本文以本仓库中npm-packages/private-demos/components-legacy/convex/README.md的函数模板文档为骨架,结合仓库内真实的函数实现、_generated生成代码与convexnpm 包源码,系统讲解 Convex 函数从定义、校验到前端调用的完整链路,并演示其在组件化 demo 中的实际用法,帮助读者直接照搬到自己的 Convex 项目中。
Convex 函数全景:Query、Mutation 与 Action
Convex 的服务器端代码全部集中在项目的convex/目录下。每个被导出的函数会自动成为部署 API 的一部分,前端通过api对象按"模块.函数名"的路径引用它。函数按能力分为三类:
| 函数类型 | 能否读库 | 能否写库 | 能否执行任意 JavaScript(含副作用) | 典型用途 |
|---|---|---|---|---|
query | ✅ | ❌ | 仅确定性代码 | 读取并派生数据,供客户端实时订阅 |
mutation | ✅ | ✅ | 仅确定性代码 | 写入数据库,业务状态变更 |
action | 间接(通过runQuery/runMutation) | 间接 | ✅ | 调用第三方 API、执行不确定/有副作用逻辑 |
从 convex/_generated/server.js 的生成代码可以看出,这三类函数在公共 API 之外,还有仅供服务端互相调用的内部版本internalQuery、internalMutation、internalAction,以及用于响应 HTTP 请求的httpAction。该文件是npx convex dev自动生成的,文件头明确提示"THIS CODE IS AUTOMATICALLY GENERATED",开发者不应手工修改它。
编写第一个 Query 函数
关联文档给出的 query 模板是函数开发的最小骨架:
// convex/myFunctions.ts import { query } from "./_generated/server"; import { v } from "convex/values"; export const myQueryFunction = query({ // Validators for arguments. args: { first: v.number(), second: v.string(), }, // Function implementation. handler: async (ctx, args) => { // Read the database as many times as you need here. const documents = await ctx.db.query("tablename").collect(); // Arguments passed from the client are properties of the args object. console.log(args.first, args.second); // Write arbitrary JavaScript here: filter, aggregate, build derived data, // remove non-public properties, or create new objects. return documents; }, });这个模板包含三个要点:
query({...})是工厂函数:它接收一个配置对象,配置对象必须包含args(参数校验器)和handler(函数实现体)两个字段。query与mutation、action一样,都从./_generated/server导入——这是类型安全的入口,它把泛型实现queryGeneric/mutationGeneric/actionGeneric与当前部署的 schema 类型绑定在一起。args声明参数契约:v.number()、v.string()来自convex/values。Convex 会在每次调用时对传入参数做运行时校验,非法参数直接拒绝执行,同时校验器也承担 TypeScript 类型推导的职责,让handler里的args.first具备精确类型。ctx.db是数据库访问句柄:ctx.db.query("tablename")按表名查询,.collect()把结果集物化为数组。Query 函数体内可以多次读取数据库,也可以做任意确定性变换(过滤、聚合、构造派生数据、剔除非公开字段、创建新对象),最终返回值会通过响应式订阅通道推送给客户端。
响应式:Query 的本质是"订阅"
query 与普通 HTTP 接口最大的区别在于响应式。前端通过useQuery订阅某个 query 后,只要 query 依赖的表发生变化,Convex 后端会重新执行该函数并把最新结果推送到客户端——开发者无需手写缓存失效或轮询。这种机制在本仓库有完整的 Rust 后端支撑,例如crates/database/src/subscription.rs与crates/database/src/query目录下即包含订阅追踪与查询执行相关实现。
在真实 demo 中读取数据
本仓库的组件化 demo 在 convex/messages.ts 中给出了一个带返回类型标注的 query 实例:
import { Doc } from "./_generated/dataModel"; export const list = query({ args: {}, handler: async (ctx): Promise<Doc<"messages">[]> => { const result = await ctx.runQuery(waitlist.index.sayGoodbyeFromQuery, {}); console.log(result); return await ctx.db.query("messages").collect(); }, });这里展示了两个进阶能力:通过Promise<Doc<"messages">[]>显式声明返回类型(Doc<"messages">由_generated/dataModel根据 schema 自动生成),以及通过ctx.runQuery在函数内部调用另一个组件暴露的 query——实现函数间(跨组件)的服务端调用。
Validator:参数校验与类型推导的底层原理
关联文档把args里的v.number()、v.string()注释为 "Validators for arguments"。这套 validator 体系并不只是 TypeScript 层的玩具,它在运行时真正生效,其实现位于 npm 包源码 convex/src/values/validators.ts 与 convex/src/values/validator.ts。
从源码结构看,所有校验器都继承自BaseValidator抽象类,该类维护了三个关键成员:
type:仅供 TypeScript 使用的类型占位,用于把校验器映射为对应的 TS 类型;isOptional:标记该字段是否可选("required"或"optional");isConvexValidator:恒为true的运行时标记,用于区分"校验器对象"与"普通对象"。
isValidator()函数正是通过检查v?.isConvexValidator === true来判断一个值是否为合法校验器(见 validator.ts)。asObjectValidator()则允许把一个"属性到校验器的映射"自动包成v.object(...)。
demo 的 schema 展示了 validator 在表结构定义中的另一种用法:
// convex/schema.ts import { defineSchema, defineTable } from "convex/server"; import { v } from "convex/values"; export default defineSchema({ messages: defineTable({ author: v.string(), body: v.string(), }), notes: defineTable({ text: v.string(), }), });defineSchema/defineTable来自convex/server,这里v.string()定义的是列的字段类型。值得注意的是源码中throwUndefinedValidatorError会专门提示 "A validator is undefined ... often caused by circular imports",提醒开发者不要在模块顶层出现循环导入导致 validator 求值失败。
编写 Mutation:写入与返回结果
关联文档给出的 mutation 模板如下:
// convex/myFunctions.ts import { mutation } from "./_generated/server"; import { v } from "convex/values"; export const myMutationFunction = mutation({ // Validators for arguments. args: { first: v.string(), second: v.string(), }, // Function implementation. handler: async (ctx, args) => { // Insert or modify documents in the database here. // Mutations can also read from the database like queries. const message = { body: args.first, author: args.second }; const id = await ctx.db.insert("messages", message); // Optionally, return a value from your mutation. return await ctx.db.get("messages", id); }, });Mutation 的要点:
ctx.db.insert(table, doc)写入:第一个参数是表名,第二个是待插入文档;返回新文档的Id。- mutation 可以同时读写:与 query 一样支持
ctx.db.query(...)读取,且所有操作处于同一个事务中,要么全部成功要么全部回滚。 - 返回值可选:
ctx.db.get(table, id)按 ID 取回文档后返回,客户端可拿到Promise化的结果。
demo 中的sendmutation 是更精简的等价写法:
export const send = mutation({ args: { body: v.string(), author: v.string(), }, handler: async (ctx, { body, author }) => { const message = { body, author }; await ctx.db.insert("messages", message); }, });这里用了解构写法({ body, author })直接提取参数,写入后不返回值(前端默认"fire and forget")。同一文件中的scheduleSendWaitlistMessage还展示了 mutation 中调用ctx.scheduler.runAfter(30 * 1000, waitlist.index.scheduleMessage, {})定时调度另一个函数的能力,以及通过ctx.db.system.query("_scheduled_functions")读取系统表验证调度结果。
事务回滚的一个真实测试场景
demo 的testPartialRollbackmutation 专门演示了跨组件调用时的部分回滚语义:
await ctx.runMutation(waitlist.index.writeSuccessfully, { text: "hello" }); try { await ctx.runMutation(waitlist.index.writeThenFail, { text: "world" }); } catch (e) { console.log("caught error", e); } const result = await ctx.runQuery(waitlist.index.latestWrite, {});它先让组件成功写入一次,再故意触发一次会失败的写入,捕获错误后查询最新写入内容——验证失败的 mutation 是否已回滚、成功的那次是否保留,这正是 mutation 事务性在组件嵌套场景下的行为测试。
React 集成:useQuery 与 useMutation
关联文档给出了函数在 React 组件中的两种调用方式。
用 useQuery 订阅数据
const data = useQuery(api.myFunctions.myQueryFunction, { first: 10, second: "hello", });useQuery的第一个参数是函数引用api.myFunctions.myQueryFunction,第二个参数是与该函数args完全匹配的参数对象;- 返回值
data的类型由函数返回类型推导而来; - 由于 query 是响应式的,
data会随数据库变化自动更新,组件无需关心请求时机。
useQuery的真实实现在 convex/src/react/client.ts(约 L922 处),它要求传入的参数必须是FunctionReference<"query">类型——这正是api对象(anyApi,见 convex/_generated/api.js)提供的类型安全引用。同类 hook 还包括useQueries(批量订阅多个 query)、usePaginatedQuery(分页查询)、useMutation、useAction与useConvex等,均在convex/src/react目录下。
用 useMutation 触发写入
const mutation = useMutation(api.myFunctions.myMutationFunction); function handleButtonPress() { // fire and forget, the most common way to use mutations mutation({ first: "Hello!", second: "me" }); // OR // use the result once the mutation has completed mutation({ first: "Hello!", second: "me" }).then((result) => console.log(result), ); }useMutation返回一个"触发函数",它有两种调用语义:
- fire and forget:直接
mutation({...})发起调用,不关心结果,这是最常见的方式,适合按钮点击等事件处理; - 等待结果:调用后拿到 Promise,通过
.then((result) => ...)或await使用 mutation 的返回值(对应 mutation handler 的return语句)。
由于useMutation返回的函数是稳定的引用(见 client.ts L1084 处实现),它可以安全地放进useEffect依赖数组或作为 props 传递,不会因组件重渲染而失效。
从模板到真实项目:组件化 demo 的函数调用链
关联文档所在的项目components-legacy是一个专门用于测试"组件 API 导入"的 demo。其 convex.config.ts 展示了组件注册方式:
import { defineApp } from "convex/server"; import waitlist from "../examples/waitlist@name-with-dashes/convex.config.js"; import nestedComponent from "./nested-component/convex.config.js"; const app = defineApp(); app.use(waitlist, { name: "waitlist" }); app.use(waitlist, { name: "waitlist2" }); app.use(nestedComponent); export default app;同一个waitlist组件被注册了两次(waitlist与waitlist2),外加一个嵌套组件nestedComponent。而 messages.ts 通过const waitlist = components.waitlist satisfies ComponentApi拿到类型安全的组件句柄后,即可用ctx.runQuery、ctx.runMutation、ctx.runAction、ctx.scheduler.runAfter跨组件调用其内部函数——这正是"函数模板文档 + 组件体系"结合后的实际形态,也印证了模板中ctx参数在多函数编排中的核心地位。
CLI 工作流:从开发到部署
关联文档最后给出两条最常用的 CLI 命令:
npx convex dev:本地开发模式。启动后会持续监听convex/目录,自动重新生成_generated代码(server.ts、api.ts、dataModel.ts等)并同步函数到本地部署,配合前端框架实现热更新开发体验;npx convex -h:在项目根目录运行,列出 Convex CLI 的全部可用命令;npx convex docs:在本地启动官方文档。
在本仓库中,_generated文件(如 convex/_generated/server.js)头部都明确写着 "To regenerate, runnpx convex dev",说明生成文件与 CLI 的绑定关系。若使用其他框架,可用npx convex dev --help查看对接参数;部署到线上环境的命令是npx convex deploy。
小结:一套模板,三种能力
关联文档的这份函数目录模板,浓缩了 Convex 开发中最核心的三件事:
- 用
query({ args, handler })定义可订阅的读函数,参数由 validator 校验并推导类型,ctx.db提供事务性读取; - 用
mutation({ args, handler })定义写函数,支持读写混合与返回值; - 在 React 中用
useQuery订阅、用useMutation触发,获得端到端类型安全与实时数据同步。
在此基础上,本仓库的components-legacydemo 展示了函数在组件体系下的进阶用法:跨组件runQuery/runMutation/runAction、定时调度、事务回滚验证等。无论你是在一个简单的单表 demo 上起步,还是在复杂的多组件应用中编排逻辑,这套模板都是进入 Convex 函数世界最直接的入口。
- 数据库
- 后端
【免费下载链接】convex-backend
The open-source reactive database for app developers
相关推荐
NV-Generate-MR API使用教程:从基础配置到高级参数调优
NV Generate MR API使用教程:从基础配置到高级参数调优 NV Generate MR是一款基于3D潜在扩散模型的尖端工具,专为生成高质量合成磁共
数据库后端SpacetimeDB keynote-2:在 convex/ 函数目录编写 Convex Query 与 Mutation 函数,并接入转账基准测试
SpacetimeDB keynote 2:在 convex/ 函数目录编写 Convex Query 与 Mutation 函数,并接入转账基准测试 本文以
数据库关系型数据库后端如何设计高性能HTTP API?http-api-design-ZH_CN的10个核心原则与实战案例
如何设计高性能HTTP API?http api design ZH_CN的10个核心原则与实战案例 HTTP API设计是现代应用开发的核心环节,一个高性能的
数据库后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考