DEV Community

Alex Spinov
Alex Spinov

Posted on

Convex Has a Free API — The Reactive Backend That Syncs Automatically

Convex is a reactive backend where queries automatically update when data changes. No polling, no WebSocket setup, no cache invalidation — just write a query and it stays in sync.

Why Convex?

  • Reactive queries — UI updates instantly when data changes
  • TypeScript end-to-end — schema, queries, mutations — all typed
  • No infrastructure — managed database, file storage, scheduled jobs
  • Free tier — generous for hobby projects

Quick Start

npm create convex@latest
npx convex dev  # Starts dev server + syncs schema
Enter fullscreen mode Exit fullscreen mode

Schema

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

export default defineSchema({
  users: defineTable({
    name: v.string(),
    email: v.string(),
    avatar: v.optional(v.string()),
  }).index('by_email', ['email']),

  messages: defineTable({
    text: v.string(),
    author: v.id('users'),
    channel: v.string(),
  }).index('by_channel', ['channel']),
});
Enter fullscreen mode Exit fullscreen mode

Queries (Reactive!)

// convex/messages.ts
import { query } from './_generated/server';
import { v } from 'convex/values';

export const list = query({
  args: { channel: v.string() },
  handler: async (ctx, { channel }) => {
    return await ctx.db
      .query('messages')
      .withIndex('by_channel', (q) => q.eq('channel', channel))
      .order('desc')
      .take(50);
  },
});
Enter fullscreen mode Exit fullscreen mode
// React component — auto-updates!
import { useQuery } from 'convex/react';
import { api } from '../convex/_generated/api';

function Chat({ channel }: { channel: string }) {
  const messages = useQuery(api.messages.list, { channel });

  return (
    <ul>
      {messages?.map((msg) => (
        <li key={msg._id}>{msg.text}</li>
      ))}
    </ul>
  );
}
Enter fullscreen mode Exit fullscreen mode

Mutations

// convex/messages.ts
import { mutation } from './_generated/server';

export const send = mutation({
  args: { text: v.string(), channel: v.string(), author: v.id('users') },
  handler: async (ctx, { text, channel, author }) => {
    await ctx.db.insert('messages', { text, channel, author });
  },
});
Enter fullscreen mode Exit fullscreen mode
import { useMutation } from 'convex/react';

function SendMessage() {
  const send = useMutation(api.messages.send);

  const handleSend = () => {
    send({ text: 'Hello!', channel: 'general', author: userId });
    // All useQuery(api.messages.list) components update INSTANTLY
  };
}
Enter fullscreen mode Exit fullscreen mode

Actions (External APIs)

import { action } from './_generated/server';

export const generateSummary = action({
  args: { text: v.string() },
  handler: async (ctx, { text }) => {
    const response = await fetch('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: { Authorization: `Bearer ${process.env.OPENAI_KEY}` },
      body: JSON.stringify({ model: 'gpt-4o-mini', messages: [{ role: 'user', content: text }] }),
    });
    return response.json();
  },
});
Enter fullscreen mode Exit fullscreen mode

Scheduled Functions

export const cleanup = mutation({
  handler: async (ctx) => {
    const old = await ctx.db.query('messages')
      .filter((q) => q.lt(q.field('_creationTime'), Date.now() - 86400000))
      .collect();
    for (const msg of old) await ctx.db.delete(msg._id);
  },
});

// Schedule in Convex dashboard or via ctx.scheduler
Enter fullscreen mode Exit fullscreen mode

Need real-time data for your app? Check out my Apify actors for web scraping feeds, or email spinov001@gmail.com for custom reactive backends.

Convex, Supabase, or Firebase — which reactive backend do you use? Share below!

Top comments (0)