home / skills / dimitrigilbert / ai-skills / convex

convex skill

/convex

This skill guides you through building full-stack TypeScript apps with Convex, covering setup, server, schema, auth, storage, frontend, testing, and deployment.

npx playbooks add skill dimitrigilbert/ai-skills --skill convex

Review the files below or copy the command above to add this skill to your agents.

Files (9)
SKILL.md
8.3 KB
---
name: convex
description: Expert Convex development assistant. Use when users want to build full-stack TypeScript apps with Convex - including setup, server functions (queries/mutations/actions), database schema, auth, file storage, real-time features, frontend integration (React, Next.js, Vue), testing, or deployment.
---

This skill provides expert guidance for building applications with Convex - the full-stack reactive database platform.

## Quick Start

When a user wants to start with Convex:

1. **New project:**
   ```bash
   npm create convex@latest
   npx convex dev
   ```

2. **Add to existing project:**
   ```bash
   npm install convex
   npx convex dev
   ```

3. **Verify:** Check dashboard at https://dashboard.convex.dev

## Core Concepts

### Three Function Types

**Queries** - Read-only, auto-reactive:
```typescript
import { query } from "./_generated/server";

export const list = query({
  args: { channelId: v.id("channels") },
  handler: async ({ db }, { channelId }) => {
    return await db.query("messages")
      .withIndex("by_channel", (q) => q.eq("channel", channelId))
      .collect();
  }
});
```

**Mutations** - Write operations, transactions:
```typescript
import { mutation } from "./_generated/server";

export const send = mutation({
  args: { body: v.string(), channel: v.id("channels") },
  handler: async ({ db }, { body, channel }) => {
    await db.insert("messages", { body, channel, createdAt: Date.now() });
  }
});
```

**Actions** - External API calls:
```typescript
import { action } from "./_generated/server";

export const summarize = action({
  args: { postId: v.id("posts") },
  handler: async (ctx, { postId }) => {
    const post = await ctx.runQuery(api.posts.get, { postId });
    const summary = await fetchLLM(post);
    await ctx.runMutation(api.posts.update, { postId, summary });
  }
});
```

### Database Operations

```typescript
// CRUD
await db.insert("table", { field: value });
const doc = await db.get(id);
await db.patch(id, { field: newValue });
await db.delete(id);

// Query methods
.collect()  // All results
.first()   // First or null
.unique()  // Exactly one (throws if not)
.paginate({ numItems: 50 })  // Pagination
```

### Schema Definition

**File:** `convex/schema.ts`

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

export default defineSchema({
  messages: defineTable({
    body: v.string(),
    author: v.id("users"),
    channel: v.id("channels"),
    createdAt: v.number()
  })
  .index("by_channel", ["channel"])
  .index("by_channel_created", ["channel", "createdAt"])
});
```

**Common types:**
- `v.string()`, `v.number()`, `v.boolean()`
- `v.id("table")` - Foreign key
- `v.array(T)`, `v.object({})`
- `v.optional(T)`, `v.union(...)`

## Frontend Integration

### React

```typescript
import { ConvexProvider, ConvexReactClient } from "convex/react";

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

<ConvexProvider client={convex}>
  <App />
</ConvexProvider>
```

**Usage:**
```typescript
import { useQuery, useMutation } from "convex/react";
import { api } from "../convex/_generated/api";

const messages = useQuery(api.messages.list, { channelId });
const sendMessage = useMutation(api.messages.send);
```

### Next.js App Router

**Server-side preloading:**
```typescript
import { preloadQuery } from "convex/nextjs";

const preloaded = await preloadQuery(api.posts.list);
return <ClientPage preloaded={preloaded} />;
```

**Client component:**
```typescript
"use client";
import { usePreloadedQuery } from "convex/react";

const posts = usePreloadedQuery(preloaded);
```

## Authentication

### Clerk (Recommended)

**Install:** `npm install @clerk/clerk-react`

**File:** `convex/auth.config.ts`
```typescript
import { AuthConfig } from "convex/server";

export default {
  providers: [{ domain: process.env.CLERK_JWT_ISSUER_DOMAIN!, applicationID: "convex" }]
} satisfies AuthConfig;
```

**Frontend:**
```typescript
import { ConvexProviderWithClerk } from "convex/react-clerk";

<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
  <App />
</ConvexProviderWithClerk>
```

**In functions:**
```typescript
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Unauthorized");
```

## File Storage

```typescript
// Upload
const storageId = await ctx.storage.store(fileBlob);
await db.insert("files", { storageId, name });

// Download
const url = await ctx.storage.getUrl(storageId);

// Delete
await ctx.storage.delete(storageId);
```

## HTTP Endpoints

**File:** `convex/http.ts`

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

const http = httpRouter();

http.route({
  path: "/webhooks/stripe",
  method: "POST",
  handler: httpAction(async (ctx, request) => {
    const payload = await request.json();
    await ctx.runMutation(internal.payments.process, { payload });
    return new Response(null, { status: 200 });
  }),
});

export default http;
```

**URL:** `https://<deployment>.convex.site`

## Scheduled Tasks

**Cron Jobs (File: `convex/crons.ts`):**
```typescript
import { cronJobs } from "convex/server";

const crons = cronJobs();
crons.interval("cleanup", { hours: 1 }, internal.tasks.cleanup);
crons.daily("digest", { hourUTC: 9 }, internal.notifications.send);
export default crons;
```

**Scheduler API (in mutations/actions):**
```typescript
// Schedule for later
await ctx.scheduler.runAfter(delayMs, internal.tasks.process, { id });

// Schedule at specific time
await ctx.scheduler.runAt(timestamp, internal.tasks.remind, { userId });
```

## Best Practices

### ✅ DO
- Use indexes for queried fields
- Filter in application code, not with `.filter()` on queries
- Validate auth in protected functions
- Use pagination for large datasets
- Use `ConvexError` for structured errors clients can handle
- Direct function calls, not multiple sequential `ctx.runQuery`

### ❌ DON'T
- Use `.filter()` on queries (use indexes or filter in code)
- Make multiple sequential `runQuery/runMutation` calls (consolidate)
- Forget to validate authentication
- Fetch entire tables without pagination

### Error Handling

```typescript
import { ConvexError } from "convex/values";

// Throw structured errors
if (!identity) {
  throw new ConvexError({ code: "UNAUTHORIZED", message: "Login required" });
}

// Client catches and handles
if (error instanceof ConvexError) {
  console.error(error.data.code, error.data.message);
}
```

## Deployment

**Production:**
```bash
npx convex deploy
```

**With frontend build:**
```bash
npx convex deploy --cmd 'npm run build'
```

**Environment:** Set `CONVEX_DEPLOY_KEY` for production

## Testing

**Install:** `npm install convex-test vitest --save-dev`

```typescript
import { convexTest } from "convex-test";

const t = convexTest(schema);

// Test mutations
await t.mutation(api.posts.create, { title: "Test" });

// Test queries
const posts = await t.query(api.posts.list);
expect(posts).toHaveLength(1);
```

## Common Issues

| Problem | Solution |
|---------|----------|
| Function not found | Run `npx convex dev` to regenerate types |
| Real-time not working | Check ConvexProvider wraps app |
| Type errors | Restart TypeScript server, regenerate types |
| Deploy fails | Verify `CONVEX_DEPLOY_KEY` is set |

## Progressive Disclosure

**Advanced topics in references:**
- **Complete setup**: [QUICK_START.md](references/QUICK_START.md)
- **Deep dive on functions**: [CORE_CONCEPTS.md](references/CORE_CONCEPTS.md)
- **Auth patterns**: [AUTH.md](references/AUTH.md)
- **Framework integration**: [FRONTEND.md](references/FRONTEND.md)
- **Performance patterns**: [BEST_PRACTICES.md](references/BEST_PRACTICES.md)
- **Production deployment**: [DEPLOYMENT.md](references/DEPLOYMENT.md)
- **Troubleshooting**: [TROUBLESHOOTING.md](references/TROUBLESHOOTING.md)
- **HTTP endpoints, cron, vector search, Convex Auth**: [ADVANCED.md](references/ADVANCED.md)

## Your Approach

1. **Assess first:** New project or existing? Framework? Goals?
2. **Start simple:** Quick start → add complexity progressively
3. **Embrace reactivity:** Leverage automatic real-time updates
4. **Type safety:** Use TypeScript validation throughout
5. **Index for performance:** Always use indexes on queried fields
6. **Validate auth:** Check authentication in protected functions
7. **Test before deploying:** Use convex-test for unit tests

Overview

This skill is an expert Convex development assistant for building full-stack TypeScript apps with Convex. It guides setup, schema design, queries/mutations/actions, auth, file storage, real-time features, frontend integration (React/Next.js/Vue), testing, and deployment. Use it to produce practical code snippets, validate architecture decisions, and troubleshoot common Convex issues.

How this skill works

I inspect your project goals and environment (new vs existing project, framework choice) and recommend a step-by-step plan: initialize or add Convex, design schema and indexes, implement server functions (queries, mutations, actions), and wire the frontend with Convex clients. I provide concrete TypeScript examples, auth and storage integration patterns, scheduler/HTTP endpoint recipes, test setups, and deployment commands. I emphasize reactivity, type safety, and performance through indexes and pagination.

When to use it

  • Starting a new TypeScript app and you want Convex as the backend
  • Adding Convex to an existing React, Next.js, or Vue project
  • Designing database schema, indexes, and relations for performance
  • Implementing server logic: queries, mutations, actions, or crons
  • Integrating auth (Clerk) or file storage and secure endpoints
  • Debugging real-time updates, type generation, or deployment failures

Best practices

  • Define schema with indexes for all frequently queried fields
  • Use queries for read-only reactive code, mutations for transactional writes, and actions for external API work
  • Validate authentication in protected functions and throw ConvexError for structured client handling
  • Avoid .filter() on queries; prefer indexed queries or post-query filtering in code
  • Paginate large queries and consolidate multiple server calls into single functions
  • Write unit tests with convex-test and run local dev to regenerate types before deploying

Example use cases

  • Realtime chat app: messages table with by_channel index, send mutation, and list query used with useQuery
  • Content workflow: server actions to call an LLM, summarize content, and store results via mutations
  • File uploads: store blobs via ctx.storage.store, save storageId in DB, and generate download URLs
  • Scheduled jobs: cleanup cron every hour and runAt/runAfter scheduled tasks from mutations
  • Webhook handling: define HTTP route to receive webhooks and route into internal mutations for processing

FAQ

How do I start a new Convex project?

Run npm create convex@latest then npx convex dev; for adding to an existing project, npm install convex and npx convex dev.

What is the difference between queries, mutations, and actions?

Queries are read-only and auto-reactive, mutations perform transactional writes, and actions are for external API calls or long-running tasks.

How should I handle authentication?

Use a provider like Clerk, validate identity in server functions via ctx.auth.getUserIdentity(), and throw ConvexError for unauthorized access.