or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced.mdauth.mddatabase.mdindex.mdnextjs.mdreact.mdschema.mdserver-functions.mdvalues-validators.md
tile.json

tessl/npm-convex

TypeScript backend SDK, client library, and CLI for building real-time applications on the Convex platform

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/convex@1.29.x

To install, run

npx @tessl/cli install tessl/npm-convex@1.29.0

index.mddocs/

Convex

TypeScript-first backend platform: real-time database + serverless functions + reactive queries.

Package: npm install convex (v1.29.3)

Quick Start

// Schema (convex/schema.ts)
import { defineSchema, defineTable } from 'convex/server';
import { v } from 'convex/values';

export default defineSchema({
  messages: defineTable({
    author: v.string(),
    body: v.string(),
  }).index('by_author', ['author']),
});

// Backend (convex/messages.ts)
import { query, mutation } from './_generated/server';
import { v } from 'convex/values';

export const list = query({
  args: {},
  handler: async (ctx) => await ctx.db.query('messages').collect(),
});

export const send = mutation({
  args: { author: v.string(), body: v.string() },
  handler: async (ctx, args) => await ctx.db.insert('messages', args),
});

// React (src/App.tsx)
import { ConvexProvider, ConvexReactClient, useQuery, useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';

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

function Messages() {
  const messages = useQuery(api.messages.list);
  const send = useMutation(api.messages.send);
  return messages?.map(m => <div key={m._id}>{m.body}</div>);
}

function App() {
  return <ConvexProvider client={convex}><Messages /></ConvexProvider>;
}

Core Imports

// Server functions
import { query, mutation, action, internalQuery, internalMutation, internalAction, httpAction } from 'convex/server';

// Schema & validation
import { defineSchema, defineTable } from 'convex/server';
import { v } from 'convex/values';

// React
import { ConvexProvider, ConvexReactClient, useQuery, useMutation, useAction, usePaginatedQuery } from 'convex/react';

// Browser (non-React)
import { ConvexClient, ConvexHttpClient } from 'convex/browser';

// Next.js
import { preloadQuery, fetchQuery, fetchMutation, fetchAction } from 'convex/nextjs';

// Auth providers
import { ConvexProviderWithAuth0 } from 'convex/react-auth0';
import { ConvexProviderWithClerk } from 'convex/react-clerk';

Function Types

Query (Read-only, Reactive)

export const get = query({
  args: { id: v.id('table') },
  handler: async (ctx, args) => {
    // ctx.db: read-only, ctx.auth, ctx.storage.getUrl()
    return await ctx.db.get(args.id);
  },
});

Mutation (Atomic Writes)

export const create = mutation({
  args: { text: v.string() },
  handler: async (ctx, args) => {
    // ctx.db: read-write (insert/patch/replace/delete)
    // ctx.scheduler: schedule functions, ctx.storage: generateUploadUrl()
    return await ctx.db.insert('table', { text: args.text });
  },
});

Action (External APIs, Non-transactional)

export const sendEmail = action({
  args: { to: v.string(), body: v.string() },
  handler: async (ctx, args) => {
    // ctx.runQuery/runMutation/runAction, ctx.vectorSearch, ctx.storage.get/store()
    await fetch('https://api.email.com/send', { method: 'POST', body: JSON.stringify(args) });
    await ctx.runMutation(api.logs.record, { event: 'email_sent' });
  },
});

Server Functions | Database Operations | Schema

Validators

// Primitives
v.string(), v.number(), v.bigint(), v.boolean(), v.null(), v.bytes()

// Structures
v.array(v.string())
v.object({ name: v.string(), age: v.number() })
v.record(v.string(), v.any())

// Special
v.id('tableName')  // Document ID
v.optional(v.string())  // Optional field
v.union(v.literal('a'), v.literal('b'))  // Union type
v.nullable(v.string())  // string | null

// Usage
const userValidator = v.object({
  name: v.string(),
  age: v.number(),
  email: v.optional(v.string()),
});
type User = Infer<typeof userValidator>;  // { name: string; age: number; email?: string }

Values & Validators

Database

// Fetch by ID
const doc = await ctx.db.get(id);

// Query
await ctx.db.query('table').collect();  // All
await ctx.db.query('table').order('desc').take(10);  // First 10
await ctx.db.query('table').filter(q => q.gt(q.field('age'), 18)).collect();

// Index query
await ctx.db.query('table')
  .withIndex('by_author', q => q.eq('author', 'Alice'))
  .collect();

// Range query
await ctx.db.query('table')
  .withIndex('by_time', q => q.gte('_creationTime', startTime).lt('_creationTime', endTime))
  .collect();

// Write (mutations only)
const id = await ctx.db.insert('table', { field: 'value' });
await ctx.db.patch(id, { field: 'newValue' });
await ctx.db.replace(id, { field: 'completelyNew' });
await ctx.db.delete(id);

Database Operations

React Hooks

// Query (reactive)
const data = useQuery(api.module.queryFn, { arg: 'value' });
if (data === undefined) return <div>Loading...</div>;

// Skip conditionally
const data = useQuery(api.module.queryFn, shouldLoad ? { arg: 'value' } : 'skip');

// Mutation
const mutateFn = useMutation(api.module.mutationFn);
await mutateFn({ arg: 'value' });

// Optimistic update
const like = useMutation(api.posts.like).withOptimisticUpdate((store, args) => {
  const posts = store.getQuery(api.posts.list);
  if (posts) {
    store.setQuery(api.posts.list, {}, posts.map(p =>
      p._id === args.postId ? { ...p, likes: p.likes + 1 } : p
    ));
  }
});

// Action
const actionFn = useAction(api.module.actionFn);
await actionFn({ arg: 'value' });

// Pagination
const { results, status, loadMore } = usePaginatedQuery(
  api.module.list,
  { filter: 'value' },
  { initialNumItems: 20 }
);

React Integration

Next.js Integration

// Server Component
import { preloadQuery } from 'convex/nextjs';
import { api } from '@/convex/_generated/api';

export default async function Page() {
  const preloaded = await preloadQuery(api.messages.list);
  return <ClientComponent preloaded={preloaded} />;
}

// Client Component
'use client';
import { usePreloadedQuery } from 'convex/react';

function ClientComponent({ preloaded }) {
  const messages = usePreloadedQuery(preloaded);  // Reactive
  return <div>{messages.map(m => <p key={m._id}>{m.body}</p>)}</div>;
}

// Server Action
import { fetchMutation } from 'convex/nextjs';

export async function createPost(formData: FormData) {
  'use server';
  const title = formData.get('title') as string;
  await fetchMutation(api.posts.create, { title });
  revalidatePath('/posts');
}

// With auth (Clerk)
import { auth } from '@clerk/nextjs';
const { getToken } = auth();
const token = await getToken({ template: 'convex' });
const data = await fetchQuery(api.users.getCurrent, {}, { token });

Next.js Integration

Authentication

// Server-side
export const getCurrentUser = query({
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) return null;
    return {
      id: identity.tokenIdentifier,
      name: identity.name,
      email: identity.email,
    };
  },
});

// React with Auth0
import { ConvexProviderWithAuth0 } from 'convex/react-auth0';
import { Auth0Provider } from '@auth0/auth0-react';

<Auth0Provider domain="..." clientId="...">
  <ConvexProviderWithAuth0 client={convex}>
    <App />
  </ConvexProviderWithAuth0>
</Auth0Provider>

// React with Clerk
import { ConvexProviderWithClerk } from 'convex/react-clerk';
import { ClerkProvider, useAuth } from '@clerk/clerk-react';

<ClerkProvider publishableKey="...">
  <ConvexProviderWithClerk client={convex} useAuth={useAuth}>
    <App />
  </ConvexProviderWithClerk>
</ClerkProvider>

// Conditional rendering
import { Authenticated, Unauthenticated } from 'convex/react';

<Authenticated><Dashboard /></Authenticated>
<Unauthenticated><Login /></Unauthenticated>

Authentication

Advanced Features

Pagination

// Backend
export const list = query({
  args: { paginationOpts: paginationOptsValidator },
  handler: async (ctx, args) =>
    await ctx.db.query('table').order('desc').paginate(args.paginationOpts),
});

// React
const { results, status, loadMore } = usePaginatedQuery(api.module.list, {}, { initialNumItems: 20 });

File Storage

// Upload
export const generateUploadUrl = mutation(async (ctx) => await ctx.storage.generateUploadUrl());

// Client
const url = await convex.mutation(api.files.generateUploadUrl);
const result = await fetch(url, { method: 'POST', body: file });
const { storageId } = await result.json();

// Download
export const getUrl = query({
  args: { storageId: v.string() },
  handler: async (ctx, args) => await ctx.storage.getUrl(args.storageId),
});

Scheduling

// Schedule function
export const schedule = mutation({
  handler: async (ctx) => {
    await ctx.scheduler.runAfter(60000, internal.task.run, { data: 'value' });
    await ctx.scheduler.runAt(timestamp, internal.task.run, { data: 'value' });
  },
});

// Cron (convex/crons.ts)
import { cronJobs } from 'convex/server';
const crons = cronJobs();
crons.daily('cleanup', { hourUTC: 2, minuteUTC: 0 }, internal.tasks.cleanup);
export default crons;

HTTP Actions

// convex/http.ts
import { httpRouter, httpAction } from 'convex/server';

const http = httpRouter();
http.route({
  path: '/webhook',
  method: 'POST',
  handler: httpAction(async (ctx, request) => {
    const body = await request.json();
    await ctx.runMutation(api.webhooks.process, body);
    return new Response(JSON.stringify({ success: true }), {
      headers: { 'Content-Type': 'application/json' },
    });
  }),
});
export default http;

Vector Search

export const search = action({
  args: { embedding: v.array(v.float64()) },
  handler: async (ctx, args) => {
    const results = await ctx.vectorSearch('documents', 'by_embedding', {
      vector: args.embedding,
      limit: 10,
      filter: q => q.eq('category', 'news'),
    });
    return results;
  },
});

Advanced Features

Type System

// Document types from schema
type Message = Doc<'messages'>;  // Includes _id, _creationTime
type MessageInput = WithoutSystemFields<Message>;  // For inserts

// Function references
api.module.functionName  // Type-safe reference
internal.module.functionName  // Internal functions

// Extract types from validators
type User = Infer<typeof userValidator>;

// Field paths
type MessageFieldPaths = FieldPaths<'messages'>;  // 'author' | 'body' | '_id' | '_creationTime'

Common Patterns

Authentication Check

const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error('Not authenticated');

Error Handling

import { ConvexError } from 'convex/values';

throw new ConvexError({ code: 'NOT_FOUND', id: args.id });

Unique Field Lookup

const user = await ctx.db.query('users')
  .withIndex('by_email', q => q.eq('email', args.email))
  .unique();

Transaction Pattern

export const transfer = mutation({
  handler: async (ctx, args) => {
    const from = await ctx.db.get(args.fromId);
    const to = await ctx.db.get(args.toId);
    // All succeed or all fail
    await ctx.db.patch(args.fromId, { balance: from.balance - args.amount });
    await ctx.db.patch(args.toId, { balance: to.balance + args.amount });
  },
});

Links

  • Server Functions - Queries, mutations, actions
  • Database - CRUD operations, queries, indexes
  • Schema - Table definitions, indexes
  • Values & Validators - Type system
  • React - Hooks and components
  • Next.js - Server-side integration
  • Auth - Authentication patterns
  • Advanced - Pagination, storage, scheduling, HTTP, vectors