https://docs.convex.dev/ai
import { query } from "./_generated/server"; import { v } from "convex/values"; export const f = query({ args: {}, returns: v.null(), handler: async (ctx, args) => { // Function body }, });
convex/http.ts
httpAction
import { httpRouter } from "convex/server"; import { httpAction } from "./_generated/server"; const http = httpRouter(); http.route({ path: "/echo", method: "POST", handler: httpAction(async (ctx, req) => { const body = await req.bytes(); return new Response(body, { status: 200 }); }), });
path
/api/someRoute
/api/someRoute
import { mutation } from "./_generated/server"; import { v } from "convex/values"; export default mutation({ args: { simpleArray: v.array(v.union(v.string(), v.number())), }, handler: async (ctx, args) => { //... }, });
import { defineSchema, defineTable } from "convex/server"; import { v } from "convex/values"; export default defineSchema({ results: defineTable( v.union( v.object({ kind: v.literal("error"), errorMessage: v.string(), }), v.object({ kind: v.literal("success"), value: v.number(), }), ), ) });
v.null()
import { query } from "./_generated/server"; import { v } from "convex/values"; export const exampleQuery = query({ args: {}, returns: v.null(), handler: async (ctx, args) => { console.log("This query returns a null value"); return null; }, });
doc._id
v.id(tableName)
null
v.null()
undefined
undefined
null
null
3n
v.int64()
bigint
3.1
v.number()
true
v.boolean()
"abc"
v.string()
new ArrayBuffer(8)
v.bytes()
ArrayBuffer
[1, 3.2, "abc"]
v.array(values)
{a: "abc"}
v.object({property: value})
{"a": "1", "b": "2"}
v.record(keys, values)
internalQuery
internalMutation
internalAction
./_generated/server
query
mutation
action
query
mutation
action
api
internal
query
internalQuery
mutation
internalMutation
action
internalAction
returns: v.null()
null
ctx.runQuery
ctx.runMutation
ctx.runAction
FunctionReference
ctx.runQuery
ctx.runMutation
ctx.runAction
export const f = query({ args: { name: v.string() }, returns: v.string(), handler: async (ctx, args) => { return "Hello " + args.name; }, }); export const g = query({ args: {}, returns: v.null(), handler: async (ctx, args) => { const result: string = await ctx.runQuery(api.example.f, { name: "Bob" }); return null; }, });
api
convex/_generated/api.ts
query
mutation
action
internal
convex/_generated/api.ts
internalQuery
internalMutation
internalAction
convex/example.ts
f
api.example.f
convex/example.ts
g
internal.example.g
convex/
h
convex/messages/access.ts
api.messages.access.h
convex/
query
mutation
action
internalQuery
internalMutation
internalAction
import { v } from "convex/values"; import { query, mutation } from "./_generated/server"; import { paginationOptsValidator } from "convex/server"; export const listWithExtraArg = query({ args: { paginationOpts: paginationOptsValidator, author: v.string() }, handler: async (ctx, args) => { return await ctx.db .query("messages") .filter((q) => q.eq(q.field("author"), args.author)) .order("desc") .paginate(args.paginationOpts); }, });
Note:
paginationOpts
numItems
v.number()
cursor
v.union(v.string(), v.null())
.paginate()
v.bigint()
v.int64()
v.record()
v.map()
v.set()
convex/schema.ts
convex/server
_creationTime
v.number()
_id
v.id(tableName)
["field1", "field2"]
Id
Id<'users'>
Record
v.record(v.id('users'), v.string())
Record<Id<'users'>, string>
Record
Id
import { query } from "./_generated/server"; import { Doc, Id } from "./_generated/dataModel"; export const exampleQuery = query({ args: { userIds: v.array(v.id("users")) }, returns: v.record(v.id("users"), v.string()), handler: async (ctx, args) => { const idToUsername: Record<Id<"users">, string> = {}; for (const userId of args.userIds) { const user = await ctx.db.get(userId); if (user) { users[user._id] = user.username; } } return idToUsername; }, });
Id<'users'>
string
as const
Array
const array: Array<T> = [...];
Record
const record: Record<KeyType, ValueType> = {...};
@types/node
package.json
const messages = await ctx.db .query("messages") .withSearchIndex("search_body", (q) => q.search("body", "hello hi").eq("channel", "#general"), ) .take(10);
filter
withIndex
.delete()
.collect()
ctx.db.delete(row._id)
.unique()
.collect()
.take(n)
for await (const row of query)
_creationTime
.order('asc')
.order('desc')
ctx.db.replace
ctx.db.patch
"use node";
ctx.db
import { action } from "./_generated/server"; export const exampleAction = action({ args: {}, returns: v.null(), handler: async (ctx, args) => { console.log("This action does not return anything"); return null; }, });
crons.interval
crons.cron
crons.hourly
crons.daily
crons.weekly
crons
import { cronJobs } from "convex/server"; import { internal } from "./_generated/api"; import { internalAction } from "./_generated/server"; const empty = internalAction({ args: {}, returns: v.null(), handler: async (ctx, args) => { console.log("empty"); }, }); const crons = cronJobs(); // Run `internal.crons.empty` every two hours. crons.interval("delete inactive users", { hours: 2 }, internal.crons.empty, {}); export default crons;
crons.ts
internal
Convex includes file storage for large files like images, videos, and PDFs.
The
ctx.storage.getUrl()
null
Do NOT use the deprecated
ctx.storage.getMetadata
Instead, query the `_storage` system table. For example, you can use `ctx.db.system.get` to get an `Id<"_storage">`.
import { query } from "./_generated/server"; import { Id } from "./_generated/dataModel"; type FileMetadata = { _id: Id<"_storage">; _creationTime: number; contentType?: string; sha256: string; size: number; } export const exampleQuery = query({ args: { fileId: v.id("_storage") }, returns: v.null(); handler: async (ctx, args) => { const metadata: FileMetadata | null = await ctx.db.system.get(args.fileId); console.log(metadata); return null; }, });
Blob
Blob
Create a real-time chat application backend with AI responses. The app should: - Allow creating users with names - Support multiple chat channels - Enable users to send messages to channels - Automatically generate AI responses to user messages - Show recent message history The backend should provide APIs for: 1. User management (creation) 2. Channel management (creation) 3. Message operations (sending, listing) 4. AI response generation using OpenAI's GPT-4 Messages should be stored with their channel, author, and content. The system should maintain message order and limit history display to the 10 most recent messages per channel.
Public Queries:
Internal Functions:
{ "name": "chat-app", "description": "This example shows how to build a chat app without authentication.", "version": "1.0.0", "dependencies": { "convex": "^1.17.4", "openai": "^4.79.0" }, "devDependencies": { "typescript": "^5.7.3" } }
{ "compilerOptions": { "target": "ESNext", "lib": ["DOM", "DOM.Iterable", "ESNext"], "skipLibCheck": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "module": "ESNext", "moduleResolution": "Bundler", "resolveJsonModule": true, "isolatedModules": true, "allowImportingTsExtensions": true, "noEmit": true, "jsx": "react-jsx" }, "exclude": ["convex"], "include": ["**/src/**/*.tsx", "**/src/**/*.ts", "vite.config.ts"] }
import { query, mutation, internalQuery, internalMutation, internalAction, } from "./_generated/server"; import { v } from "convex/values"; import OpenAI from "openai"; import { internal } from "./_generated/api"; /** * Create a user with a given name. */ export const createUser = mutation({ args: { name: v.string(), }, returns: v.id("users"), handler: async (ctx, args) => { return await ctx.db.insert("users", { name: args.name }); }, }); /** * Create a channel with a given name. */ export const createChannel = mutation({ args: { name: v.string(), }, returns: v.id("channels"), handler: async (ctx, args) => { return await ctx.db.insert("channels", { name: args.name }); }, }); /** * List the 10 most recent messages from a channel in descending creation order. */ export const listMessages = query({ args: { channelId: v.id("channels"), }, returns: v.array( v.object({ _id: v.id("messages"), _creationTime: v.number(), channelId: v.id("channels"), authorId: v.optional(v.id("users")), content: v.string(), }), ), handler: async (ctx, args) => { const messages = await ctx.db .query("messages") .withIndex("by_channel", (q) => q.eq("channelId", args.channelId)) .order("desc") .take(10); return messages; }, }); /** * Send a message to a channel and schedule a response from the AI. */ export const sendMessage = mutation({ args: { channelId: v.id("channels"), authorId: v.id("users"), content: v.string(), }, returns: v.null(), handler: async (ctx, args) => { const channel = await ctx.db.get(args.channelId); if (!channel) { throw new Error("Channel not found"); } const user = await ctx.db.get(args.authorId); if (!user) { throw new Error("User not found"); } await ctx.db.insert("messages", { channelId: args.channelId, authorId: args.authorId, content: args.content, }); await ctx.scheduler.runAfter(0, internal.index.generateResponse, { channelId: args.channelId, }); return null; }, }); const openai = new OpenAI(); export const generateResponse = internalAction({ args: { channelId: v.id("channels"), }, returns: v.null(), handler: async (ctx, args) => { const context = await ctx.runQuery(internal.index.loadContext, { channelId: args.channelId, }); const response = await openai.chat.completions.create({ model: "gpt-4o", messages: context, }); const content = response.choices[0].message.content; if (!content) { throw new Error("No content in response"); } await ctx.runMutation(internal.index.writeAgentResponse, { channelId: args.channelId, content, }); return null; }, }); export const loadContext = internalQuery({ args: { channelId: v.id("channels"), }, returns: v.array( v.object({ role: v.union(v.literal("user"), v.literal("assistant")), content: v.string(), }), ), handler: async (ctx, args) => { const channel = await ctx.db.get(args.channelId); if (!channel) { throw new Error("Channel not found"); } const messages = await ctx.db .query("messages") .withIndex("by_channel", (q) => q.eq("channelId", args.channelId)) .order("desc") .take(10); const result = []; for (const message of messages) { if (message.authorId) { const user = await ctx.db.get(message.authorId); if (!user) { throw new Error("User not found"); } result.push({ role: "user" as const, content: `${user.name}: ${message.content}`, }); } else { result.push({ role: "assistant" as const, content: message.content }); } } return result; }, }); export const writeAgentResponse = internalMutation({ args: { channelId: v.id("channels"), content: v.string(), }, returns: v.null(), handler: async (ctx, args) => { await ctx.db.insert("messages", { channelId: args.channelId, content: args.content, }); return null; }, });
import { defineSchema, defineTable } from "convex/server"; import { v } from "convex/values"; export default defineSchema({ channels: defineTable({ name: v.string(), }), users: defineTable({ name: v.string(), }), messages: defineTable({ channelId: v.id("channels"), authorId: v.optional(v.id("users")), content: v.string(), }).index("by_channel", ["channelId"]), });
export default function App() { return <div>Hello World</div>; }