How to Use Convex: The Beginner's Guide (First Schema, First Query, Real-Time UI, Auth, and Deploy)

CodexFuse Team · 2026-10-31T03:57:27.257Z · 15 min read

How to Use Convex: The Beginner's Guide (First Schema, First Query, Real-Time UI, Auth, and Deploy)

Learning how to use Convex comes down to one idea: your backend can be plain TypeScript instead of a separate server. Convex is a reactive backend platform that combines a real-time database with server functions, so you write queries and mutations in TypeScript, deploy them with one command, and your React UI updates the moment the data changes — no API routes, no WebSocket wiring, no caching layer to build by hand. Create a project, define your first schema, and you can have a live, syncing database behind your app in a single sitting.

This Convex backend for beginners tutorial walks the complete arc in one guide: what Convex actually is (and how it differs from a traditional backend), starting your first project with the Convex CLI, writing your first schema in schema.ts, creating queries, mutations, and actions, reading real-time data in React with useQuery, adding authentication with Clerk, deploying to production, an honest Convex vs Supabase vs Firebase comparison, what the free tier actually includes, and the beginner mistakes to avoid. If you want to poke at HTTP APIs while you build, a free REST API tester in your browser lets you inspect requests and responses without writing a test script.

What Is the Fastest Way to Start Using Convex?

Install the package, run the dev command, and you are coding against a live cloud backend in under two minutes.

  1. Create a project with a frontend (or scaffold one): npm create convex@latest gets you the quickest start.
  2. Install the Convex library in an existing app: npm install convex.
  3. Start the dev loop: npx convex dev — it asks you to log in with GitHub, creates a project, and saves your deployment URLs.
  4. Write your backend API in the new convex/ folder: convex/tasks.ts with an exported query becomes api.tasks.get.
  5. Connect your app: wrap it in <ConvexProvider> with a ConvexReactClient pointed at your deployment URL.
  6. Read data in real time: const tasks = useQuery(api.tasks.get) — the UI re-renders automatically when the data changes.
  7. Ship it: npx convex deploy pushes your functions and schema to production.

What Is Convex, Exactly?

Convex is an all-in-one backend and database designed around reactivity. The official description from the Convex Developer Hub calls it "an all-in-one backend and database that integrates quickly and easily" with frameworks like Next.js. You write backend logic as TypeScript functions — queries to read, mutations to write, actions to call external services — and Convex handles the database, the real-time sync, and the hosting of those functions.

The "reactive" part is the whole pitch. In a normal backend, your React app fetches data, and it stays stale until you refetch. With Convex, useQuery subscribes to your data: when a mutation changes a document, every subscribed client re-renders with the new value automatically. There is no polling interval to tune and no WebSocket code to write — the sync is the default behavior, not a feature you bolt on.

Two things shape how you should think about Convex as a beginner. First, it was built by former Dropbox infrastructure engineers specifically for real-time applications (per Novemind's 2026 platform review), so real-time sync is a first-class primitive, not a plugin. Second, Convex open-sourced its backend in late 2025 and added a first-party self-hosted path (per BitDoze's 2026 self-hosting guide), so the cloud version and the self-hosted version are the same codebase — you are not locked into one deployment model.

How Do You Start a Convex Project?

Run npm create convex@latest for the fastest path, or add Convex to an existing app by installing the convex package and running npx convex dev. That command prompts you to log in with GitHub, creates a Convex project, and saves your production and deployment URLs (per the official Next.js quickstart on docs.convex.dev).

The dev command also creates a convex/ folder in your project, which is where every backend function lives. While npx convex dev keeps running, it watches that folder and syncs your functions to your dev deployment in the cloud — you edit a file, save, and the backend updates without a redeploy step. If you are new to Git workflows, our GitHub beginner guide covers the account setup and pushing code you will need around this flow.

Next, connect the frontend. Create a ConvexReactClient with your deployment URL and wrap your app in <ConvexProvider>:

"use client";
import { ConvexProvider, ConvexReactClient } from "convex/react";

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

export function ConvexClientProvider({ children }: { children: React.ReactNode }) {
  return <ConvexProvider client={convex}>{children}</ConvexProvider>;
}

That URL comes from the environment — keep it in a .env.local file and never hardcode it into source files you commit.

How Do You Write Your First Convex Schema?

Create convex/schema.ts and describe your tables with defineSchema and defineTable, using the validator builder v from convex/values for every field. The official schemas documentation shows the canonical shape:

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

export default defineSchema({
  tasks: defineTable({
    text: v.string(),
    isCompleted: v.boolean(),
    assignee: v.optional(v.string()),
  }).index("by_completed", ["isCompleted"]),
});

Convex automatically adds _id and _creationTime to every document, and your schema is pushed automatically by both npx convex dev and npx convex deploy. The first push after a schema change validates all existing documents against it — if any document fails, the push fails, so add fields as v.optional(...) when a table already has data.

Strictly speaking, a schema is optional: you can use Convex schema-less for rapid prototyping, and the official docs even recommend starting without one. But once your shape solidifies, adding schema.ts buys you runtime validation plus end-to-end TypeScript types across your app — the validator builder doubles as your type system, so a v.string() field is a real string on the client.

The index in the example matters more than it looks. Convex queries filter efficiently only through declared indexes — .index("by_completed", ["isCompleted"]) in the schema lets you later call .withIndex("by_completed", (q) => q.eq("isCompleted", false)). Fetching everything with .collect() and filtering in JavaScript works on toy data and collapses at real scale; declare the index and use withIndex from the start.

What Are Convex Queries, Mutations, and Actions?

Every Convex backend is built from three function types, and the rule for which one to use is simple: queries read, mutations write, actions talk to the outside world.

FunctionWhat it doesKey rule
QueryReads data, powers real-time subscriptionsRead-only; useQuery re-runs it automatically when data changes
MutationWrites data, runs transactionally with ACID guaranteesThe only place that calls ctx.db writes; captures user intent
ActionCalls third-party APIs (Stripe, OpenAI), runs Node.jsCannot touch ctx.db directly — reads/writes via ctx.runQuery / ctx.runMutation

A query looks like this — the args/returns validators are the current recommended syntax:

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

export const list = query({
  args: {},
  returns: v.array(v.object({ _id: v.id("tasks"), text: v.string(), isCompleted: v.boolean() })),
  handler: async (ctx) => {
    return await ctx.db.query("tasks").collect();
  },
});

Mutations write with ctx.db.insert, ctx.db.patch, and ctx.db.delete, and every mutation runs as a transaction — no partial writes, no inconsistent state. Actions are the escape hatch: they run in Convex's JavaScript environment (with fetch) or in Node.js (with the "use node" directive for npm packages), time out after 10 minutes, and get 64MB (Convex runtime) or 512MB (Node.js) of memory, per the official actions documentation.

Teach yourself the action rule before you build anything real: an action's context does not include ctx.db. To read data, an action calls ctx.runQuery(internal.myFunctions.readData, {...}); to write, ctx.runMutation(internal.myFunctions.writeData, {...}) — usually an internal function the client cannot call directly. The official docs also flag a common anti-pattern: calling an action directly from a client button. The recommended flow is client → mutation (which records the intent in the database) → mutation schedules the action via ctx.scheduler. That way the mutation enforces invariants — like preventing the same action from running twice.

How Do You Read Real-Time Data in React?

Call useQuery(api.tasks.list) inside any component under <ConvexProvider>, and you are done. The hook returns the current data and re-renders your component automatically whenever a mutation changes the underlying documents — that is the entire real-time implementation on the client side.

"use client";
import { useQuery, useMutation } from "convex/react";
import { api } from "../convex/_generated/api";

export default function TaskList() {
  const tasks = useQuery(api.tasks.list);
  const toggle = useMutation(api.tasks.toggle);

  if (tasks === undefined) return <p>Loading…</p>;

  return (
    <ul>
      {tasks.map((t) => (
        <li key={t._id}>
          <button onClick={() => toggle({ id: t._id })}>
            {t.isCompleted ? "✅" : "⬜"} {t.text}
          </button>
        </li>
      ))}
    </ul>
  );
}

Two details save beginners real debugging time. First, useQuery returns undefined while the first result loads — check for it, or your .map crashes on mount. Second, mutations are optimistic in feel but real on the wire: the moment toggle commits, every other browser showing that list updates too, because the server pushes the change to all subscribed queries.

How Do You Add Authentication with Clerk?

Convex's documented auth pairing is Clerk, and the integration is explicit rather than automatic. The official Convex & Clerk guide walks nine steps: create a Clerk application, activate the Convex integration inside the Clerk dashboard, create convex/auth.config.ts with your Clerk issuer domain, run npx convex dev to sync the config, install @clerk/react (or @clerk/nextjs), set your publishable key, and replace <ConvexProvider> with <ConvexProviderWithClerk>.

The server-side config is one file:

// convex/auth.config.ts
import { AuthConfig } from "convex/server";

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

Once that is in place, any Convex function can call ctx.auth.getUserIdentity() — it returns the user info from the JWT, or null when the caller is not authenticated. The standard beginner pattern is to pair the identity check with an indexed query:

export const mine = query({
  args: {},
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    if (identity === null) throw new Error("Not authenticated");
    return await ctx.db
      .query("tasks")
      .withIndex("by_assignee", (q) => q.eq("assignee", identity.email))
      .collect();
  },
});

On the client, Convex ships <Authenticated>, <Unauthenticated>, <AuthLoading>, and <AuthRefreshing> helper components so you can render a sign-in button for guests and the real app for logged-in users without writing auth-state plumbing yourself. One warning straight from the docs: the component calling an authenticated query must be a child of <Authenticated>, or it throws on page load.

How Do You Deploy Convex to Production?

Run npx convex deploy. That command pushes your functions and your schema to your production deployment in one step — including the automatic schema validation that blocks the deploy if existing documents no longer match. Your dev deployment (used by npx convex dev) and your production deployment are separate, so experimenting locally never touches live data.

Production configuration lives on the Convex dashboard: environment variables like CLERK_JWT_ISSUER_DOMAIN are set there rather than in local files, and the deploy key authenticates your CI pipeline. The frontend itself goes wherever you normally host it — the official docs point to Vercel or Netlify, both of which deploy on every git push. Keep your backend and frontend deploys in the same commit rhythm so the schema and the code that reads it never drift apart.

If you would rather own the infrastructure, Convex added a first-party self-hosted path: a Docker Compose setup running the open-source backend against Postgres (Neon documents the integration with Lakebase Postgres). Per BitDoze's 2026 self-hosting guide, a $5–10 VPS is enough for a solo deployment — our Docker beginner guide and VPS hosting guide cover the pieces you would need.

How Does Convex Compare to Supabase and Firebase?

These three are the comparison every beginner actually wants, and they sit in different corners: Convex is TypeScript-reactive, Supabase is Postgres-relational, Firebase is Google-scale document storage.

ConvexSupabaseFirebase
Data modelDocuments, schema in TypeScriptPostgres tables, SQLDocuments/collections, schemaless
Backend codeTypeScript functions (queries, mutations, actions)Edge Functions (Deno/TypeScript) + SQLCloud Functions (Node.js) + security rules
Real-timeAutomatic via useQuery subscriptionsOpt-in realtime channels per tableOpt-in realtime listeners
Auth pairingClerk integration documented first-classBuilt-in auth, or bring your ownBuilt-in Firebase Auth
Self-hostingFirst-party Docker Compose path (2025+)First-party Docker pathEmulator suite; not truly self-hostable
Free tier1M function calls/mo, 512MB DB500MB DB, 50K monthly active usersSpark plan with generous quotas, pay-as-you-go Blaze

The honest decision rule: pick Convex when you want TypeScript everywhere and real-time sync without thinking about it; pick Supabase when your data is relational and you want real SQL (our Supabase beginner guide walks its setup); pick Firebase when you are already deep in the Google ecosystem and need its mobile-first SDKs. None of them is "better" — they are built for different shapes of app.

How Much Does Convex Cost?

The free tier is real and usable for side projects: 1 million function calls per month, 20 GB-hours of action compute, 0.5GB (512MB) of database storage, 1GB of file storage, and 1GB/month of bandwidth — recurring monthly allowances, corroborated across srvrlss.io's Convex provider page, the dev.to Convex guide, and BitDoze's 2026 review. The free plan supports 1–6 developers and includes preview deployments and a selectable data region.

PlanPriceIncludedBest for
Free & Starter$0 + pay-as-you-go overages1M function calls/mo, 512MB DB, 1GB file storagePersonal projects and prototypes
Professional$25 per developer / month25M function calls, 250 GB-hours compute, 50GB DB, 100GB file storage, daily backups, custom domainsStartups going to production
Business & Enterprise$2,500 monthly minimumSAML/SSO, SLAs, dedicated deployments, custom MSATeams at scale

Overage pricing on the metered side is published (per convex.dev/enterprise/pricing): $2 per additional million function calls, $0.20 per GB/month of database storage, $0.03 per GB/month of file storage, $0.12 per GB of data egress, and $0.16–0.32 per GB-hour of action compute depending on runtime. Convex also runs a startup program with up to a year of free Professional access for early-stage companies (per ToolQuestor's 2026 Convex review) — worth checking if you are building a funded prototype.

Beginner Mistakes to Avoid

MistakeWhy it hurtsDo this instead
Filtering with .filter() after .collect()Loads the whole table into memory on every readDeclare .index(...) in the schema and query with .withIndex(...)
Touching ctx.db inside an actionActions do not have database access — the function failsCall ctx.runQuery / ctx.runMutation on an internal function
Calling an action directly from a client buttonNo invariant enforcement; double-submits can double-run the actionClient calls a mutation; the mutation records intent and schedules the action
Tightening the schema on a table with old dataThe deploy-time validation fails and the whole deploy is blockedAdd new fields as v.optional(...), migrate data first, then tighten
Forgetting applicationID: "convex" in auth.config.tsClerk tokens fail validation and every authed query throwsCopy the config exactly from the Convex & Clerk guide

Frequently Asked Questions

Do I need a schema to use Convex?

No — Convex works schema-less, and the official docs recommend starting without one for rapid prototyping. But adding schema.ts gives you runtime validation and end-to-end TypeScript types, and schemas are pushed automatically by npx convex dev and npx convex deploy. Add it once your data shape stops changing.

Can Convex actions touch the database directly?

No. Actions interact with the database only indirectly, through ctx.runQuery and ctx.runMutation — usually on internal queries and mutations the client cannot call. Queries and mutations get ctx.db; actions get fetch, Node.js, and the scheduler. Design your backend around this split from day one.

How does Convex compare to Firebase Realtime Database?

Both give you live-updating data, but the developer model differs. Convex is TypeScript functions + a document database with schema validation and auth via Clerk; Firebase is Google infrastructure + schemaless documents with security rules and built-in Firebase Auth. Convex's real-time layer is automatic through useQuery, while Firebase requires explicit realtime listeners.

Is Convex free?

Yes, for personal projects and prototypes: the hosted free tier includes 1 million function calls per month, 20 GB-hours of action compute, 512MB of database storage, and 1GB of file storage, and the open-source backend can be self-hosted for free (you pay only for your own server). Usage beyond the free allowances or paid team features costs extra.

Can I self-host Convex?

Yes — Convex open-sourced its backend and ships a first-party Docker Compose setup, with documented Postgres (Lakebase) integration from Neon. The self-hosted build is the same codebase the cloud runs, so switching between cloud and self-hosted does not require rewriting your backend. Per 2026 guides, a small $5–10 VPS handles a solo deployment.

Sources

  • Convex Developer Hub — Next.js Quickstart (docs.convex.dev/quickstart/nextjs): npm create convex@latest, npx convex dev GitHub login flow, convex/ folder, ConvexProvider setup
  • Convex Developer Hub — Schemas (docs.convex.dev/database/schemas): defineSchema/defineTable, validator builder v, automatic _id/_creationTime, index declarations, schema pushed by dev and deploy, schemaValidation option
  • Convex Developer Hub — Actions (docs.convex.dev/functions/actions): indirect DB access via runQuery/runMutation, Node.js "use node" directive, 10-minute timeout, 64MB/512MB memory limits, mutation-schedules-action anti-pattern guidance
  • Convex Developer Hub — Convex & Clerk (docs.convex.dev/auth/clerk): Clerk dashboard integration, auth.config.ts with applicationID "convex", ConvexProviderWithClerk, getUserIdentity null check, Authenticated/Unauthenticated/AuthLoading helpers
  • convex.dev/pricing — plan structure: Free plan built-in resources, Starter pay-as-you-go beyond free tier, Professional per-developer pricing
  • convex.dev/enterprise/pricing — metered rates: $2/million function calls, $0.20/GB database storage, $0.03/GB file storage, $0.12/GB egress, $0.16–0.32/GB-hour action compute
  • srvrlss.io Convex provider page (crawled October 2026) — Free plan recurring allowances: 1M function calls/mo, 20 GB-hours action compute, 0.5GB database storage, 1GB file storage; Professional: 25M calls, 250 GB-hours, 50GB DB, 100GB file storage
  • dev.to/@0012303 "Convex Has a Free Backend API" (March 2026) — free tier table: 512MB database storage, 1GB/month bandwidth, 1M function calls/month, included real-time sync
  • BitDoze "Self-Host Convex in 2026" (October 2026) — Docker Compose self-host, $5–10 VPS sizing, backend open-sourced November 2025, Professional at $25/developer/month
  • Novemind "Convex Backend Platform: Real-Time Development Guide 2026" — founded by former Dropbox infrastructure engineers, raised €22M, reactive sync positioning
  • Neon "Getting started with Convex and Neon" (October 2026) — Convex self-hosting via Docker Compose with Lakebase Postgres, admin key generation, dashboard at localhost:6791
  • EliteAI Convex Backend skill reference (crawled October 2026) — current function syntax: always include args/returns validators, use withIndex not filter, internal functions, actions cannot access ctx.db
Featured on Twelve Tools Launch Llama Tools Launch your MCP