Convex
Convex is a backend with a database, server functions in TypeScript, file storage, scheduling and auth. A client that subscribes to a query gets the new result when the data changes, with no WebSocket code to write.
Hackathon perks:To be confirmed
Good to know
- When data changes, every client subscribed to that query gets the new result.
- Official clients for React Native and Expo, Swift (iOS) and Kotlin (Android).
- Queries and mutations are TypeScript functions. Convex publishes agent guidelines, skills and an MCP server.
Get started
- 1
Create the app and install Convex
npx create-expo-app my-app cd my-app && npm install convex - 2
Start a dev deployment
This asks you to log in with GitHub, creates a project, saves its URL to .env.local and creates the convex/ folder. Leave it running: it syncs your functions as you edit.
npx convex dev - 3
Write a query
Each export becomes an API function. This one is api.tasks.get.
convex/tasks.tsimport { query } from "./_generated/server"; export const get = query({ args: {}, handler: async (ctx) => { return await ctx.db.query("tasks").collect(); }, }); - 4
Connect the app
app/_layout.tsximport { ConvexProvider, ConvexReactClient } from "convex/react"; import { Stack } from "expo-router"; const convex = new ConvexReactClient(process.env.EXPO_PUBLIC_CONVEX_URL!, { unsavedChangesWarning: false, }); export default function RootLayout() { return ( <ConvexProvider client={convex}> <Stack> <Stack.Screen name="index" /> </Stack> </ConvexProvider> ); }app/index.tsximport { api } from "@/convex/_generated/api"; import { useQuery } from "convex/react"; import { Text, View } from "react-native"; export default function Index() { const tasks = useQuery(api.tasks.get); return ( <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}> {tasks?.map(({ _id, text }) => ( <Text key={_id}>{text}</Text> ))} </View> ); } - 5
Write data with a mutation
convex/tasks.tsimport { mutation } from "./_generated/server"; import { v } from "convex/values"; export const createTask = mutation({ args: { text: v.string() }, handler: async (ctx, args) => { return await ctx.db.insert("tasks", { text: args.text }); }, }); - 6
Teach your agent Convex
Installs Convex guidelines and adds managed sections to AGENTS.md and CLAUDE.md, which Capy reads.
npx convex ai-files install
Add a Convex schema with users and moments tables, an index on moments by author, a query for the current user's moments, and a mutation to add one. Use Convex Auth with email sign-in. Run npx convex dev --once and fix any errors it reports.
Swift (iOS)Add the ConvexMobile package and subscribe to a query.
- 1
Add the package
In Xcode, add the package https://github.com/convex/convex-swift and pick the ConvexMobile product. Create the backend with npm init -y, npm install convex and npx convex dev, then copy the URL.
cat .env.local | grep CONVEX_URL - 2
Subscribe to a query
ContentView.swiftimport ConvexMobile let convex = ConvexClient(deploymentUrl: "YOUR_CONVEX_URL") struct Todo: Decodable { let _id: String let text: String } struct ContentView: View { @State private var todos: [Todo] = [] var body: some View { List { ForEach(todos, id: \._id) { todo in Text(todo.text) } }.task { for await todos: [Todo] in convex.subscribe(to: "tasks:get") .replaceError(with: []).values { self.todos = todos } }.padding() } }
Kotlin (Android)Add the Android client and collect a query as state.
- 1
Add the dependency
Use min SDK 26 and add the INTERNET permission to AndroidManifest.xml.
build.gradle.ktsplugins { kotlin("plugin.serialization") version "1.9.0" } dependencies { implementation("dev.convex:android-convexmobile:0.8.0@aar") { isTransitive = true } implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") } - 2
Subscribe to a query
MainActivity.kt@Serializable data class Task(val text: String) val client: ConvexClient by lazy { ConvexClient(YOUR_CONVEX_URL) } @Composable fun Tasks(modifier: Modifier = Modifier) { val tasks: Result<List<Task>> by client.subscribe<List<Task>>("tasks:get") .collectAsState(Result.success(listOf())) LazyColumn(modifier = modifier) { items(tasks.getOrElse { emptyList() }) { task -> Text(text = task.text) } } }
Auth, files and the MCP serverWhat you will want by the afternoon.
- 1
Auth
Convex Auth is the recommended option for new projects: email and password, passkeys, and Apple, Google or GitHub sign-in. Clerk also works with React Native.
Convex auth docs - 2
Photos and files
A mutation returns an upload URL from storage.generateUploadUrl(), the app posts the file to it, and a second mutation saves the storageId.
Upload files - 3
MCP server
Lets an agent read tables, logs and run functions on your dev deployment.
npx -y convex@latest mcp start