---
name: convex-reactive-backend
description: "Build a Convex backend — schema, reactive queries, mutations, actions, crons, and live React data with zero cache invalidation. Production Convex backend: defineSchema/defineTable with v validators and indexes, queries and mutations in convex/ with ctx.db, actions for external fetch/Node side effects, useQuery/useMutation hooks that update reactively without manual invalidation, scheduler ctx.scheduler.runAfter, crons.ts for scheduled functions, file storage, and ConvexProvider wiring into Next.js App Router."
license: MIT
---

# Convex Reactive Backend

This skill scaffolds and edits a production [Convex](https://convex.dev) backend from inside your project: a typed schema, server-side queries and mutations, actions for external I/O, scheduled functions, and the React hooks that bind it all to your UI. It's for developers building real-time apps (dashboards, chat, collaborative tools) who want a TypeScript backend without writing API endpoints, websocket plumbing, or cache-invalidation logic.

## When to use it

- You're starting a new app and want a typed data layer where the frontend updates the instant data changes.
- You need server functions that read/write the database (`mutation`) plus ones that call third-party APIs or run Node code (`action`).
- You want scheduled work — sending a reminder N minutes after signup, nightly cleanup — via the scheduler or cron jobs.
- You're tired of `react-query` invalidation keys and want subscriptions handled for you.

## How it works

Functions live in the `convex/` directory and are deployed by `npx convex dev`, which watches files, pushes code, and regenerates types in `convex/_generated/`.

- **Schema:** `convex/schema.ts` uses `defineSchema` / `defineTable` with the `v` validator (`v.string()`, `v.id("users")`, `v.optional(...)`) and `.index("by_author", ["authorId"])` for fast lookups.
- **Queries & mutations:** Export `query`/`mutation` from `./_generated/server`. Read with `ctx.db.query("messages").withIndex(...).collect()`; write with `ctx.db.insert`/`patch`/`replace`/`delete`. Queries are reactive and cached; mutations are transactional.
- **Actions:** Use `action` for `fetch`, randomness, or `"use node";` code. Actions can't touch `ctx.db` directly — they call queries/mutations via `ctx.runQuery` / `ctx.runMutation`.
- **Scheduling:** `ctx.scheduler.runAfter(ms, api.module.fn, args)` for delayed jobs; `convex/crons.ts` with `crons.interval` / `crons.cron` for recurring ones.
- **React:** `useQuery(api.messages.list)` subscribes and re-renders on change; `useMutation(api.messages.send)` returns a callable.

## Usage

```bash
curl -fsSL --create-dirs -o ~/.claude/skills/convex-reactive-backend/SKILL.md https://claudeskil.com/skills/convex-reactive-backend/SKILL.md
npm install convex
npx convex dev   # provisions a dev deployment, watches convex/
```

```ts
// convex/messages.ts
import { query, mutation } from "./_generated/server";
import { v } from "convex/values";

export const list = query({
  args: { channel: v.string() },
  handler: (ctx, { channel }) =>
    ctx.db.query("messages").withIndex("by_channel", q => q.eq("channel", channel)).collect(),
});

export const send = mutation({
  args: { channel: v.string(), body: v.string() },
  handler: (ctx, args) => ctx.db.insert("messages", args),
});
```

## Tips & gotchas

- Define an index for any field you filter on; `.filter()` scans, `.withIndex()` doesn't.
- Mutations run in a transaction and may retry — keep them deterministic and side-effect-free; push `fetch`/email/Stripe into actions.
- Actions aren't transactional and can run more than once; make external calls idempotent.
- Wrap your tree in `<ConvexProvider client={convex}>`; `useQuery` returns `undefined` while loading, not `null`.

## FAQ

**Do I write REST endpoints?** No. Exported functions are callable directly through generated, typed clients — though `convex/http.ts` exists if you need raw HTTP webhooks.

**How is real-time handled?** Convex tracks which queries read which data and pushes updates over a websocket; subscribed `useQuery` components re-render automatically.

**Can I use my existing Postgres?** Convex is its own document store, not a Postgres wrapper. Migrate data in via mutations or an import script.
