Back to Blog
convexbackendrealtimetypescript

Building Real-Time Features with Convex

How to build reactive, real-time features using Convex — from live cursors to instant data sync — without managing WebSocket infrastructure.

Aditya Vikram Mahendru3 min read
Building Real-Time Features with Convex

Why Convex?

Convex is a full-stack platform that combines a real-time database, serverless functions, and reactive queries into a single SDK. The killer feature: data automatically syncs to every connected client without polling or manual WebSocket management.

The Traditional Approach

Building real-time features traditionally requires:

  • A database (PostgreSQL, MongoDB)
  • A WebSocket server
  • Pub/Sub infrastructure (Redis, RabbitMQ)
  • Client-side state management
  • Optimistic update logic

With Convex

Write a query function, use it from React — data stays synchronized automatically:

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

export const getProjects = query({
  args: { category: v.optional(v.string()) },
  handler: async (ctx, args) => {
    if (args.category) {
      return await ctx.db
        .query("projects")
        .filter(q => q.eq(q.field("category"), args.category))
        .collect()
    }
    return await ctx.db.query("projects").collect()
  },
})
// components/ProjectList.tsx
import { useQuery } from "convex/react"
import { api } from "@/convex/_generated/api"

export function ProjectList() {
  const projects = useQuery(api.projects.getProjects, { category: "web" })
  if (projects === undefined) return <div>Loading...</div>
  return projects.map(p => <ProjectCard key={p._id} project={p} />)
}

Reactive Queries

Every useQuery hook subscribes to changes. When any mutation modifies data that the query depends on, Convex automatically re-runs the query and pushes the new result to all connected clients.

// convex/submissions.ts
import { mutation } from "./_generated/server"

export const submitContact = mutation({
  args: {
    name: v.string(),
    email: v.string(),
    message: v.string(),
  },
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity()
    // Rate limiting built-in
    await ctx.db.insert("submissions", {
      ...args,
      userId: identity?.subject,
      createdAt: Date.now(),
    })
  },
})

Real-Time Collaborative Features

Live Cursors

// convex/cursors.ts
export const updateCursor = mutation({
  args: { x: v.number(), y: v.number() },
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity()
    await ctx.db.insert("cursors", {
      userId: identity?.subject,
      x: args.x,
      y: args.y,
      updatedAt: Date.now(),
    })
  },
})

export const getActiveCursors = query({
  handler: async (ctx) => {
    const fiveSecondsAgo = Date.now() - 5000
    return await ctx.db
      .query("cursors")
      .filter(q => q.gte(q.field("updatedAt"), fiveSecondsAgo))
      .collect()
  },
})

Schema Design

Define your database schema in TypeScript:

// convex/schema.ts
import { defineSchema, defineTable } from "convex/server"
import { v } from "convex/values"

export default defineSchema({
  projects: defineTable({
    title: v.string(),
    description: v.string(),
    category: v.string(),
    technologies: v.array(v.string()),
    imageUrl: v.optional(v.string()),
  }).index("by_category", ["category"]),

  testimonials: defineTable({
    name: v.string(),
    role: v.string(),
    content: v.string(),
    avatar: v.optional(v.string()),
  }),
})

When to Use Convex

Use CaseConvex FitAlternative
Real-time dashboards✅ ExcellentWebSockets + Redis
Collaborative editing✅ ExcellentCRDTs + WebRTC
Contact forms✅ Great (built-in rate limiting)Serverless functions
Blog CMS✅ GoodHeadless CMS
File storage⚠️ Needs external serviceS3 / Cloudinary
Heavy analytics⚠️ Query limitsData warehouse

Conclusion

Convex eliminates the infrastructure complexity of building real-time features. For projects where live data sync is critical — collaboration tools, live dashboards, interactive UIs — it's one of the fastest paths to production.