Convex Client and Backend SDK
Convex is a comprehensive backend application platform offering a real-time database, serverless functions (queries, mutations, actions), and client libraries for JavaScript/TypeScript, with strong support for React. The current stable version is 1.36.0, with frequent precompiled releases indicating rapid development and continuous improvements. A key differentiator is its real-time reactivity, where client-side `useQuery` hooks automatically update whenever the underlying database data changes, eliminating manual subscription management. It provides end-to-end type safety, optional schema definitions, and a TypeScript-first approach for both backend function definitions and client-side consumption. The platform includes SDKs for defining backend logic, integrating with React, and handling authentication with providers like Auth0 and Clerk. Convex aims to simplify full-stack development by unifying the database and backend logic within a single reactive environment.
Common errors
-
Error: VITE_CONVEX_URL is not defined
cause The environment variable for the Convex deployment URL is missing or incorrectly named.fixCreate a `.env` file in your project root with `VITE_CONVEX_URL=https://your-convex-url.convex.cloud` (or `CONVEX_URL` for Node.js environments) and restart your development server. -
ctx.auth.getUserIdentity() returns null in a query
cause The client-side authentication process has not completed by the time the `useQuery` hook attempts to fetch data, or the user is not signed in.fixEnsure the user is authenticated before calling queries that rely on `ctx.auth.getUserIdentity()`. Use Convex's `Authenticated` React component or handle the `null` identity gracefully within your client-side component, showing a loading or unauthenticated state. -
Type errors in api imports
cause The TypeScript types for your Convex backend functions (located in `convex/_generated/api.d.ts`) are out of sync with your latest backend code.fixRun `npx convex dev` (or `npx convex codegen` if not running `dev`) to regenerate the TypeScript types. Ensure your `convex/` directory is properly set up. -
Write conflict: Optimistic concurrency control
cause A Convex mutation failed to commit because the underlying data it read changed due to another concurrent mutation. This leads to retries and can indicate contention on specific documents.fixRefactor mutations to read less data, use more specific indexed queries, or reduce concurrent writes to the same document. For high-contention scenarios, rethink the data model to spread writes across more documents.
Warnings
- breaking Convex function arguments changed from multiple positional arguments to a single arguments object. Additionally, the schema builder `s` moved from `convex/schema` to `v` in `convex/values`, and several client APIs (`ConvexReactClient`, `ConvexHttpClient`) were updated for consistency.
- breaking The `ctx.db.get`, `patch`, `replace`, and `delete` functions now require the table name as the first argument, e.g., `ctx.db.get("tableName", id)`. While previous syntax is still supported, it will be deprecated.
- deprecated Node.js 18 support is being deprecated, with new projects defaulting to Node.js 20. Existing Node.js 18 projects will be automatically migrated to Node.js 20 by October 22, 2025.
- gotcha When using `useQuery` within React components, `ctx.auth.getUserIdentity()` can initially return `null` if the Convex client has not yet fully authenticated, even if the user is logged in.
- gotcha React Hooks cannot be called conditionally. Using `useQuery` inside an `if` statement or conditional block will lead to runtime errors.
- gotcha Circular imports in your Convex backend files, especially involving `schema.ts`, can lead to 'Undefined validator' errors at runtime.
Install
-
npm install convex -
yarn add convex -
pnpm add convex
Imports
- ConvexReactClient
const ConvexReactClient = require('convex/react');import { ConvexReactClient } from 'convex/react'; - useQuery
import useQuery from 'convex/react';
import { useQuery } from 'convex/react'; - query
import { query } from 'convex/react';import { query } from 'convex/server'; - mutation
import { mutation } from 'convex';import { mutation } from 'convex/server'; - v
import { s } from 'convex/schema';import { v } from 'convex/values'; - api
import { api } from '../convex/_generated/api';
Quickstart
import React from 'react';
import { ConvexReactClient, ConvexProvider, useQuery } from 'convex/react';
import { api } from '../convex/_generated/api'; // Adjust path as needed
// Initialize the Convex client
const convexUrl = process.env.VITE_CONVEX_URL ?? 'https://your-convex-url.convex.cloud'; // Replace with your actual URL or env var
const convex = new ConvexReactClient(convexUrl);
// Backend function definition (e.g., in convex/tasks.ts)
/*
import { query } from './_generated/server';
import { v } from 'convex/values';
export const getTasks = query({
args: { status: v.optional(v.string()) },
handler: async (ctx, args) => {
return await ctx.db.query('tasks')
.filter(q => args.status ? q.eq(q.field('status'), args.status) : true)
.collect();
},
});
*/
interface Task {
_id: string;
text: string;
status: 'todo' | 'done';
}
function TaskList() {
// Fetch tasks in real-time. The component re-renders when data changes.
const tasks = useQuery(api.tasks.getTasks, { status: 'todo' });
if (tasks === undefined) {
return <div>Loading tasks...</div>;
}
if (tasks.length === 0) {
return <div>No tasks to display.</div>;
}
return (
<div>
<h1>Todo List</h1>
<ul>
{tasks.map((task: Task) => (
<li key={task._id}>{task.text}</li>
))}
</ul>
</div>
);
}
export default function App() {
return (
<ConvexProvider client={convex}>
<TaskList />
</ConvexProvider>
);
}