Skip to content
moai-platform-convex logo

Moai Platform Convex

moai-platform-convex

Convex real-time backend specialist covering TypeScript-first reactive patterns, optimistic updates, and server functions. Use when building real-time collaborative apps.

mattnigh/skills_collection0installs24stars

SKILL.md

Full skill instructions

moai-platform-convex: Convex Real-time Backend Specialist

Quick Reference (30 seconds)

Convex is a real-time reactive backend platform with TypeScript-first design, automatic caching, and optimistic updates.

When to Use Convex

  • Real-time collaborative applications (docs, whiteboards, chat)
  • Apps requiring instant UI updates without manual refetching
  • TypeScript-first projects needing end-to-end type safety
  • Applications with complex optimistic update requirements

Core Concepts

Server Functions: queries (read), mutations (write), actions (external APIs) Reactive Queries: Automatic re-execution when underlying data changes Optimistic Updates: Instant UI updates before server confirmation Automatic Caching: Built-in query result caching with intelligent invalidation

Quick Start

npm create convex@latest
npx convex dev

Context7 Library: /​get-convex/​convex


Implementation Guide

Project Structure

my-app/
  convex/
    _generated/         # Auto-generated types and API
    schema.ts           # Database schema definition
    functions/          # Server functions by domain
    http.ts             # HTTP endpoints (optional)
    crons.ts            # Scheduled jobs (optional)
  src/
    ConvexProvider.tsx  # Client setup

Schema Definition

// convex/​schema.ts
import { defineSchema, defineTable } from 'convex/​server'
import { v } from 'convex/​values'

export default defineSchema({
  documents: defineTable({
    title: v.string(),
    content: v.string(),
    ownerId: v.string(),
    isPublic: v.boolean(),
    createdAt: v.number(),
    updatedAt: v.number()
  })
    .index('by_owner', ['ownerId'])
    .index('by_public', ['isPublic', 'createdAt'])
    .searchIndex('search_content', {
      searchField: 'content',
      filterFields: ['ownerId', 'isPublic']
    }),

  collaborators: defineTable({
    documentId: v.id('documents'),
    userId: v.string(),
    permission: v.union(v.literal('read'), v.literal('write'))
  })
    .index('by_document', ['documentId'])
    .index('by_user', ['userId'])
})

Validators (v module)

import { v } from 'convex/​values'

// Primitives: v.string(), v.number(), v.boolean(), v.null(), v.int64(), v.bytes()
// Complex: v.array(v.string()), v.object({...}), v.union(...), v.optional(...)
// References: v.id('tableName')

Query Functions (Reactive)

import { query } from '../​_generated/​server'
import { v } from 'convex/​values'

export const list = query({
  args: { ownerId: v.string() },
  handler: async (ctx, args) => {
    return await ctx.db
      .query('documents')
      .withIndex('by_owner', (q) => q.eq('ownerId', args.ownerId))
      .order('desc')
      .collect()
  }
})

export const getById = query({
  args: { id: v.id('documents') },
  handler: async (ctx, args) => {
    const doc = await ctx.db.get(args.id)
    if (!doc) throw new Error('Document not found')
    return doc
  }
})

export const searchContent = query({
  args: { searchQuery: v.string(), limit: v.optional(v.number()) },
  handler: async (ctx, args) => {
    return await ctx.db
      .query('documents')
      .withSearchIndex('search_content', (q) =>
        q.search('content', args.searchQuery).eq('isPublic', true)
      )
      .take(args.limit ?? 10)
  }
})

Mutation Functions

import { mutation } from '../​_generated/​server'
import { v } from 'convex/​values'

export const create = mutation({
  args: { title: v.string(), content: v.string(), isPublic: v.boolean() },
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity()
    if (!identity) throw new Error('Unauthorized')
    return await ctx.db.insert('documents', {
      ...args,
      ownerId: identity.subject,
      createdAt: Date.now(),
      updatedAt: Date.now()
    })
  }
})

export const update = mutation({
  args: { id: v.id('documents'), title: v.optional(v.string()), content: v.optional(v.string()) },
  handler: async (ctx, args) => {
    const { id, ...updates } = args
    const existing = await ctx.db.get(id)
    if (!existing) throw new Error('Document not found')
    const identity = await ctx.auth.getUserIdentity()
    if (existing.ownerId !== identity?.subject) throw new Error('Forbidden')
    await ctx.db.patch(id, { ...updates, updatedAt: Date.now() })
  }
})

export const remove = mutation({
  args: { id: v.id('documents') },
  handler: async (ctx, args) => await ctx.db.delete(args.id)
})

Action Functions (External APIs)

import { action } from '../​_generated/​server'
import { internal } from '../​_generated/​api'
import { v } from 'convex/​values'

export const generateSummary = action({
  args: { documentId: v.id('documents') },
  handler: async (ctx, args) => {
    const doc = await ctx.runQuery(internal.documents.getById, { id: args.documentId })
    const response = await fetch('https://api.openai.com/​v1/​completions', {
      method: 'POST',
      headers: { 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`, 'Content-Type': 'application/​json' },
      body: JSON.stringify({ model: 'gpt-4', prompt: `Summarize: ${doc.content}`, max_tokens: 150 })
    })
    const result = await response.json()
    await ctx.runMutation(internal.documents.updateSummary, { id: args.documentId, summary: result.choices[0].text })
    return result.choices[0].text
  }
})

React Client Setup

import { ConvexProvider, ConvexReactClient } from 'convex/​react'
import { ConvexProviderWithClerk } from 'convex/​react-clerk'

const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL)

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <ConvexProviderWithClerk client={convex} useAuth={useAuth}>
      {children}
    </​ConvexProviderWithClerk>
  )
}

React Hooks Usage

import { useQuery, useMutation } from 'convex/​react'
import { api } from '../​../​convex/​_generated/​api'

export function DocumentList({ userId }: { userId: string }) {
  const documents = useQuery(api.functions.documents.list, { ownerId: userId })
  const createDocument = useMutation(api.functions.documents.create)

  if (documents === undefined) return <Loading />

  return (
    <div>
      <button onClick={() => createDocument({ title: 'New', content: '', isPublic: false })}>
        New Document
      </​button>
      {documents.map((doc) => <DocumentCard key={doc._id} document={doc} />)}
    </​div>
  )
}

Advanced Patterns

Optimistic Updates

import { useMutation } from 'convex/​react'
import { api } from '../​../​convex/​_generated/​api'

export function useOptimisticUpdate() {
  return useMutation(api.functions.documents.update)
    .withOptimisticUpdate((localStore, args) => {
      const { id, ...updates } = args
      const existing = localStore.getQuery(api.functions.documents.getById, { id })
      if (existing) {
        localStore.setQuery(api.functions.documents.getById, { id }, {
          ...existing, ...updates, updatedAt: Date.now()
        })
      }
    })
}

File Storage

// Server-side
export const generateUploadUrl = mutation({
  handler: async (ctx) => await ctx.storage.generateUploadUrl()
})

export const saveFile = mutation({
  args: { storageId: v.id('_storage'), fileName: v.string() },
  handler: async (ctx, args) => await ctx.db.insert('files', { ...args, uploadedAt: Date.now() })
})

export const getFileUrl = query({
  args: { storageId: v.id('_storage') },
  handler: async (ctx, args) => await ctx.storage.getUrl(args.storageId)
})
// Client-side upload
export function useFileUpload() {
  const generateUploadUrl = useMutation(api.functions.files.generateUploadUrl)
  const saveFile = useMutation(api.functions.files.saveFile)

  return async (file: File) => {
    const uploadUrl = await generateUploadUrl()
    const response = await fetch(uploadUrl, { method: 'POST', headers: { 'Content-Type': file.type }, body: file })
    const { storageId } = await response.json()
    await saveFile({ storageId, fileName: file.name })
    return storageId
  }
}

Scheduled Functions (Crons)

import { cronJobs } from 'convex/​server'
import { internal } from './​_generated/​api'

const crons = cronJobs()
crons.interval('cleanup old drafts', { hours: 24 }, internal.documents.cleanupOldDrafts)
crons.cron('daily analytics', '0 0 * * *', internal.analytics.generateDailyReport)
export default crons

HTTP Endpoints

import { httpRouter } from 'convex/​server'
import { httpAction } from './​_generated/​server'

const http = httpRouter()
http.route({
  path: '/​webhook/​stripe',
  method: 'POST',
  handler: httpAction(async (ctx, request) => {
    const body = await request.text()
    await ctx.runMutation(internal.payments.processWebhook, { body, signature: request.headers.get('stripe-signature') })
    return new Response('OK', { status: 200 })
  })
})
export default http

Authentication (Clerk)

export const current = query({
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity()
    if (!identity) return null
    return await ctx.db.query('users').withIndex('by_token', (q) => q.eq('tokenIdentifier', identity.tokenIdentifier)).first()
  }
})

export const ensureUser = mutation({
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity()
    if (!identity) throw new Error('Unauthorized')
    const existing = await ctx.db.query('users').withIndex('by_token', (q) => q.eq('tokenIdentifier', identity.tokenIdentifier)).first()
    if (existing) return existing._id
    return await ctx.db.insert('users', { tokenIdentifier: identity.tokenIdentifier, email: identity.email, name: identity.name, createdAt: Date.now() })
  }
})

Error Handling

import { ConvexError } from 'convex/​values'

export const secureOperation = mutation({
  args: { id: v.id('documents') },
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity()
    if (!identity) throw new ConvexError('UNAUTHORIZED')
    const doc = await ctx.db.get(args.id)
    if (!doc) throw new ConvexError({ code: 'NOT_FOUND', message: 'Document not found' })
  }
})

Best Practices

Query Optimization:

  • Use indexes for all filtered queries
  • Prefer paginated queries for large datasets
  • Use search indexes for full-text search

Mutation Design:

  • Keep mutations focused and atomic
  • Use internal mutations for multi-step operations
  • Validate all inputs with the v module

Works Well With

  • moai-platform-supabase - Alternative PostgreSQL-based backend
  • moai-lang-typescript - TypeScript patterns and best practices
  • moai-domain-frontend - React integration patterns
  • moai-quality-security - Authentication and authorization patterns

Status: Production Ready Generated with: MoAI-ADK Skill Factory v2.0 Last Updated: 2025-12-07 Platform: Convex Real-time Backend

More skills from mattnigh

spring-framework-patterns logo
mattnigh/skills_collection

spring-framework-patterns

Comprehensive Spring Framework and Spring Boot best practices including dependency injection patterns, bean lifecycle and scopes, REST API development, Spring Data JPA, service layer design, Spring Security, testing strategies, caching, AOP, async processing, error handling, and common anti-patte...

24 0
View

TypeScript厳格モードによる型安全性設計を専門とするスキル。 📖 参照書籍: - 『Effective TypeScript』(Dan Vanderkam): 型設計 📚 リソース参照: - `resources/Level1_basics.md`: レベル1の基礎ガイド - `resources/Level2_intermediate.md`: レベル2の実務ガイド - `resources/Level3_advanced.md`: レベル3の応用ガイド - `resources/Level4_expert.md`: レベル4の専門ガイド - `resources/di...

24 0
View
deploying-cloud-k8s logo
mattnigh/skills_collection

deploying-cloud-k8s

Deploys applications to cloud Kubernetes (AKS/GKE/DOKS) with CI/CD pipelines. Use when deploying to production, setting up GitHub Actions, troubleshooting deployments. Covers build-time vs runtime vars, architecture matching, and battle-tested debugging.

24 0
View
moai-alfred-agent-guide logo
mattnigh/skills_collection

moai-alfred-agent-guide

19-agent team structure, decision trees for agent selection, Haiku vs Sonnet model selection, and agent collaboration principles. Use when deciding which sub-agent to invoke, understanding team responsibilities, or learning multi-agent orchestration.

24 0
View
mermaid-diagram-generator logo
mattnigh/skills_collection

mermaid-diagram-generator

Converts architecture descriptions, module specs, or workflow docs into Mermaid diagrams. Use when visualizing brick module relationships, workflows (DDD, investigation), or system architecture. Supports: flowcharts, sequence diagrams, class diagrams, state machines, entity relationship diagrams,...

24 0
View
pre-flight-check logo
mattnigh/skills_collection

pre-flight-check

INVOKE FIRST before any code work. Validates git workflow (branch, issue, worklog) and checks approach. Use at START of every task and END before completing. Prevents skipped steps.

24 0
View
browser-dev-tools logo
mattnigh/skills_collection

browser-dev-tools

This skill should be used when working with browser-rendered artifacts (i.e. Bun, React/React Native) to proactively validate that development work on localhost is being built appropriately and to support debugging browser-rendered content. Use this skill after making frontend changes to verify t...

24 0
View
lead-dev logo
mattnigh/skills_collection

lead-dev

Lead Développeur - Coordination technique opérationnelle, code review, mentoring et livraison. Pair de web-dev-process au niveau OPÉRATIONS.

24 0
View
worker-monitor logo
mattnigh/skills_collection

worker-monitor

Monitor Docker workers, RQ queue health, and auto-scale workers (max 4). Use when: checking job progress, monitoring queue depth, scaling workers up/down, diagnosing slow processing, or waiting for jobs to complete. Referenced by data-quality skill during reprocessing.

24 0
View
quality-advisor logo
mattnigh/skills_collection

quality-advisor

Proactive quality guidance system that monitors artifact creation and provides real-time feedback on documentation quality

24 0
View
bump-version logo
mattnigh/skills_collection

bump-version

This skill should be used when the user wants to bump the version number in the workspace. It updates versions across all pyproject.toml files (root, tde, and tda packages) and the CHANGELOG.md to keep them synchronized.

24 0
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View