mindrally

convex

3
0
# Install this skill:
npx skills add Mindrally/skills --skill "convex"

Install specific skill from multi-skill repository

# Description

Guidelines for developing with Convex backend-as-a-service platform, covering queries, mutations, actions, and real-time data patterns

# SKILL.md


name: convex
description: Guidelines for developing with Convex backend-as-a-service platform, covering queries, mutations, actions, and real-time data patterns


Convex Development Guidelines

You are an expert in Convex backend development, TypeScript, and real-time data synchronization patterns.

General Development Specifications

Code Style and Structure

  • Write concise TypeScript using functional declarations, iterators, and modules
  • Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError)
  • Structure code with exported components, subcomponents, helpers, and static types
  • Use dash-case for directories with named exports
  • Prefer interfaces over types; avoid enums in favor of union types
  • Use functional components with declarative JSX patterns

Error Handling

  • Handle errors early in functions with guard clauses
  • Log errors appropriately for debugging
  • Provide user-friendly error messages
  • Use Zod for form validation
  • Implement proper error boundaries in React components

UI Framework Integration

  • Use Shadcn UI and Radix UI for component primitives
  • Style with Tailwind CSS using responsive, mobile-first design
  • Minimize useClient, useEffect, and useState usage
  • Leverage React Server Components where applicable
  • Use Suspense for loading states and dynamic loading for code splitting

Convex-Specific Patterns

Queries

Structure queries using the query constructor:

import { query } from "./_generated/server";
import { v } from "convex/values";

export const getItems = query({
  args: {
    status: v.optional(v.string()),
  },
  handler: async (ctx, args) => {
    // ctx provides: db, storage, auth
    const identity = await ctx.auth.getUserIdentity();

    if (args.status) {
      return await ctx.db
        .query("items")
        .withIndex("by_status", (q) => q.eq("status", args.status))
        .collect();
    }

    return await ctx.db.query("items").collect();
  },
});

Important: Prefer Convex indexes over filters for better performance. Define indexes in schema.ts using the .index() method, then query with .withIndex().

Mutations

Structure mutations for database writes:

import { mutation } from "./_generated/server";
import { v } from "convex/values";

export const createItem = mutation({
  args: {
    title: v.string(),
    description: v.optional(v.string()),
  },
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) {
      throw new Error("Not authenticated");
    }

    return await ctx.db.insert("items", {
      title: args.title,
      description: args.description,
      userId: identity.subject,
      createdAt: Date.now(),
    });
  },
});

Actions

Use actions for external API calls and side effects:

import { action } from "./_generated/server";
import { v } from "convex/values";

export const sendEmail = action({
  args: {
    to: v.string(),
    subject: v.string(),
    body: v.string(),
  },
  handler: async (ctx, args) => {
    // Actions can call external APIs
    const response = await fetch("https://api.email-service.com/send", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(args),
    });

    return response.ok;
  },
});

Schema Definition with Indexes

import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  items: defineTable({
    title: v.string(),
    description: v.optional(v.string()),
    status: v.string(),
    userId: v.string(),
    createdAt: v.number(),
  })
    .index("by_status", ["status"])
    .index("by_user", ["userId"])
    .index("by_user_and_status", ["userId", "status"]),
});

HTTP Router

Define HTTP routes for webhooks and external integrations:

import { httpRouter } from "convex/server";
import { httpAction } from "./_generated/server";

const http = httpRouter();

http.route({
  path: "/webhook",
  method: "POST",
  handler: httpAction(async (ctx, request) => {
    const body = await request.json();
    // Process webhook
    return new Response(JSON.stringify({ success: true }), {
      status: 200,
      headers: { "Content-Type": "application/json" },
    });
  }),
});

export default http;

Scheduled Jobs

Implement cron jobs for recurring tasks:

import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";

const crons = cronJobs();

// Run every hour
crons.interval(
  "cleanup-old-items",
  { hours: 1 },
  internal.tasks.cleanupOldItems
);

// Run at specific time (daily at midnight UTC)
crons.monthly(
  "monthly-report",
  { day: 1, hourUTC: 0, minuteUTC: 0 },
  internal.reports.generateMonthlyReport
);

export default crons;

File Handling

Three-step process for file uploads:

// 1. Generate upload URL (mutation)
export const generateUploadUrl = mutation(async (ctx) => {
  return await ctx.storage.generateUploadUrl();
});

// 2. Client POSTs file to the URL
// const uploadUrl = await generateUploadUrl();
// const response = await fetch(uploadUrl, { method: "POST", body: file });
// const { storageId } = await response.json();

// 3. Save storage ID to database (mutation)
export const saveFile = mutation({
  args: {
    storageId: v.id("_storage"),
    filename: v.string(),
  },
  handler: async (ctx, args) => {
    return await ctx.db.insert("files", {
      storageId: args.storageId,
      filename: args.filename,
    });
  },
});

Best Practices

  1. Always use indexes for queries that filter or sort data
  2. Validate arguments using Convex validators (v.string(), v.number(), etc.)
  3. Check authentication early in handlers that require it
  4. Use internal functions for operations that should not be exposed to clients
  5. Leverage real-time subscriptions - Convex queries automatically update when data changes
  6. Keep mutations small and focused on single operations
  7. Use actions for side effects - never call external APIs from queries or mutations
  8. Handle errors gracefully with proper error messages for users

Performance Considerations

  • Use .withIndex() instead of .filter() whenever possible
  • Paginate large result sets using .paginate()
  • Use .first() instead of .collect() when expecting a single result
  • Consider data denormalization for frequently accessed data
  • Use Convex's built-in caching - avoid implementing your own

# Supported AI Coding Agents

This skill is compatible with the SKILL.md standard and works with all major AI coding agents:

Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.