Home
RequiredThe backend

Convex

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. 1

    Create the app and install Convex

    npx create-expo-app my-app
    cd my-app && npm install convex
  2. 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. 3

    Write a query

    Each export becomes an API function. This one is api.tasks.get.

    convex/tasks.ts
    import { query } from "./_generated/server";
    
    export const get = query({
      args: {},
      handler: async (ctx) => {
        return await ctx.db.query("tasks").collect();
      },
    });
  4. 4

    Connect the app

    app/_layout.tsx
    import { 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.tsx
    import { 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. 5

    Write data with a mutation

    convex/tasks.ts
    import { 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. 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
Ask Capy for the backend
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. 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. 2

    Subscribe to a query

    ContentView.swift
    import 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. 1

    Add the dependency

    Use min SDK 26 and add the INTERNET permission to AndroidManifest.xml.

    build.gradle.kts
    plugins {
        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. 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. 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. 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. 3

    MCP server

    Lets an agent read tables, logs and run functions on your dev deployment.

    npx -y convex@latest mcp start
NextCandle