Convex for React Native: Why I Switched from Supabase and How to Set It Up
March 2026
Convex is a reactive backend that replaces Supabase and Firebase for React Native apps. It handles auth, database, and serverless functions in one SDK -- no stitching together separate services, no managing real-time subscriptions manually, no writing raw SQL migrations. You define your schema in TypeScript, and Convex generates type-safe queries, mutations, and actions that work out of the box with Expo.
I switched Ship React Native from Supabase to Convex after months of fighting with Edge Functions, manual subscription management, and auth token refresh headaches. This post covers why I made that switch, how to set up Convex with React Native and Expo, and the honest tradeoffs you should know about before choosing Convex for your next project.
Table of Contents
What Is Convex in React?
Convex is a backend-as-a-service built around reactivity. Instead of writing REST endpoints that you poll for updates, you write query functions that automatically re-run whenever the underlying data changes. In React and React Native, this means your UI stays in sync with your database without any manual subscription management.
The core idea: you define functions on the server (queries, mutations, and actions), and Convex generates a typed client that you use in your React Native components. When you call useQuery with a Convex query function, the component re-renders automatically whenever the data changes. No WebSocket setup, no manual cache invalidation, no onSnapshot listeners to clean up.
If you've used Firebase's real-time database, the concept is similar -- but with full TypeScript safety, a relational-style schema, and server functions that run in a proper runtime instead of Cloud Functions' cold start nightmare. For React Native developers, the Convex React client works identically to the web version. You install the same convex package, wrap your app in a ConvexProvider, and use the same hooks everywhere.
Why I Switched from Supabase to Convex
I used Supabase for over a year across multiple apps and in the original version of Ship React Native. Supabase is a solid product, and I don't regret using it. But after shipping several production apps, I kept hitting the same friction points.
Real-time reactivity that actually works
Supabase has real-time subscriptions, but they're bolted on. You set up a channel, subscribe to changes on specific tables, handle the events manually, and remember to unsubscribe on cleanup. In a complex app with multiple screens listening to different data, this becomes a mess of subscription management code.
With Convex, reactivity is the default. Every useQuery call is automatically reactive. When a mutation changes data that a query depends on, every component using that query re-renders with fresh data. No channels, no event handlers, no cleanup functions. It just works.
Auth + Database + Functions in one SDK
With Supabase, you're working with three somewhat separate systems: Supabase Auth, the Postgres database (via the client library or raw SQL), and Edge Functions for server-side logic. Each has its own API, its own error handling patterns, and its own deployment process.
Convex gives you all three in a single SDK. Auth is configured alongside your schema. Mutations and queries run against the database directly. Actions can call external APIs. Everything is deployed together with npx convex dev during development and npx convex deploy for production.
Type-safe queries generated from schema
This was the tipping point. In Supabase, you can generate TypeScript types from your schema, but it's a manual step (supabase gen types typescript), the types live separately from your query logic, and they don't prevent you from referencing columns that don't exist.
In Convex, your schema is TypeScript, your functions are TypeScript, and the generated client knows exactly what arguments each function takes and what it returns. Rename a field in your schema and you get type errors everywhere that field is referenced. The feedback loop is immediate.
Simpler server functions for AI integrations
This is where the difference was most dramatic for me. Ship React Native includes 8 AI functions (text generation, image generation, text-to-speech, speech-to-text, vision analysis, translation, summarization, and chat). With Supabase Edge Functions, each one required its own Deno Deploy function, separate configuration, and a cold start that users could feel.
Convex actions run in a managed runtime with no cold start problem for most use cases. I define an action, import the OpenAI SDK, call the API, store the result, and the client automatically gets the update through its reactive query. The entire round trip is noticeably faster and requires significantly less code.
Setting Up Convex with Expo
Getting Convex running with a React Native Expo project takes about five minutes. Here's the process:
First, install the Convex package:
npx expo install convex
Initialize Convex in your project:
npx convex init
This creates a convex/ directory in your project root. This is where all your backend code lives -- schema, functions, auth configuration, everything.
Start the Convex development server:
npx convex dev
This command watches your convex/ directory for changes, deploys them to Convex's cloud, and generates typed client code automatically. You run this alongside npx expo start during development.
Next, wrap your app with the Convex provider. In your root layout or App component:
import { ConvexProvider, ConvexReactClient } from "convex/react";
const convex = new ConvexReactClient(process.env.EXPO_PUBLIC_CONVEX_URL!);
export default function App() {
return (
<ConvexProvider client={convex}>
{/ Your app /}
</ConvexProvider>
);
}
Add your Convex URL to your .env file:
EXPO_PUBLIC_CONVEX_URL=https://your-project-123.convex.cloud
That's it. You now have a reactive backend connected to your Expo app. No Supabase dashboard to configure, no Firebase console to navigate, no database migrations to run manually.
Convex Auth for React Native
Convex Auth supports the providers that matter for mobile apps: Google Sign-In, Apple Sign-In, Magic Link (email), and Anonymous auth. The setup is handled through configuration in your convex/auth.config.ts file.
Here's how Convex auth compares to Supabase Auth in practice:
Google and Apple Sign-In: With Supabase, you configure OAuth providers in the Supabase dashboard, handle the OAuth flow in your app with a library like expo-auth-session, then exchange the token with Supabase. With Convex, you configure the providers in your auth config file and use the Convex auth client directly. The flow is similar but there's one less system to configure.
Magic Link: Both Supabase and Convex support passwordless email auth. The implementation is comparable. Convex handles it through its auth actions, Supabase through its built-in auth endpoints.
Anonymous Auth: This is where Convex wins for mobile apps. Anonymous auth lets users start using your app immediately without signing up. They get a real user record in your database, and when they later sign in with Google or Apple, their anonymous account merges with their new identity. This is critical for mobile conversion -- every friction point in onboarding costs you users.
The authentication state is available everywhere through the useConvexAuth hook:
import { useConvexAuth } from "convex/react";
function ProfileScreen() {
const { isAuthenticated, isLoading } = useConvexAuth();
if (isLoading) return <LoadingSpinner />;
if (!isAuthenticated) return <LoginScreen />;
return <UserProfile />;
}
Token refresh, session persistence, and state management are all handled by the SDK. No more debugging expired Supabase JWTs at 2am.
Database with Real-Time Subscriptions
The Convex database is document-based (similar to Firestore) but with a proper schema system and relational capabilities through references. You define your schema in 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(),
plan: v.union(v.literal("free"), v.literal("pro")),
}),
generations: defineTable({
userId: v.id("users"),
prompt: v.string(),
result: v.string(),
model: v.string(),
createdAt: v.number(),
}).index("by_user", ["userId"]),
});
The useQuery hook is where Convex really shines for React Native. Any component that calls useQuery automatically re-renders when the underlying data changes:
import { useQuery } from "convex/react";
import { api } from "../convex/_generated/api";
function GenerationHistory() {
const generations = useQuery(api.generations.listByUser);
return (
<FlatList
data={generations}
renderItem={({ item }) => <GenerationCard generation={item} />}
/>
);
}
When another part of your app (or another user, or a server action) inserts a new generation, this FlatList updates automatically. No polling. No manual refetching. No stale data bugs.
Compare this to Supabase, where you'd need to set up a real-time subscription channel, handle the INSERT event, manually update your local state or cache, and clean up the subscription when the component unmounts. That's easily 30-40 lines of boilerplate that Convex replaces with a single hook call.
Convex Functions: Actions, Mutations, and Queries
Convex has three types of server functions, and understanding when to use each one is key:
Queries are read-only functions that access the database. They're reactive -- any component using a query automatically re-renders when the query's data changes. Queries must be deterministic (no side effects, no external API calls).
// convex/generations.ts
import { query } from "./_generated/server";
import { v } from "convex/values";
export const listByUser = query({
args: { userId: v.id("users") },
handler: async (ctx, args) => {
return await ctx.db
.query("generations")
.withIndex("by_user", (q) => q.eq("userId", args.userId))
.order("desc")
.take(20);
},
});
Mutations are write operations that modify the database. They're transactional -- if a mutation fails partway through, all changes are rolled back. Like queries, they can't call external APIs.
export const createGeneration = mutation({
args: {
prompt: v.string(),
result: v.string(),
model: v.string(),
},
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Not authenticated");
return await ctx.db.insert("generations", {
userId: identity.subject,
prompt: args.prompt,
result: args.result,
model: args.model,
createdAt: Date.now(),
});
},
});
Actions are where you call external APIs -- OpenAI, Stripe, any third-party service. Actions can also call mutations and queries internally. This is what replaces Supabase Edge Functions.
export const generateText = action({
args: { prompt: v.string() },
handler: async (ctx, args) => {
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: args.prompt }],
});
const result = response.choices[0].message.content;
await ctx.runMutation(api.generations.createGeneration, {
prompt: args.prompt,
result: result,
model: "gpt-4o",
});
return result;
},
});
The beauty of this architecture is separation of concerns. Queries and mutations are fast, transactional, and reactive. Actions handle the slow, unpredictable external world. And everything is type-safe end to end.
File Storage
Convex includes built-in file storage, which is useful for user uploads, generated images, audio files, and other binary data. You upload files through the Convex client, get a storage ID back, and reference that ID in your database documents.
const generateUploadUrl = useMutation(api.files.generateUploadUrl);
async function uploadImage(uri: string) {
const uploadUrl = await generateUploadUrl();
const response = await fetch(uri);
const blob = await response.blob();
await fetch(uploadUrl, {
method: "POST",
headers: { "Content-Type": blob.type },
body: blob,
});
}
Files are served through Convex's CDN. No need to configure a separate storage bucket in AWS S3 or Supabase Storage. For most mobile apps, the built-in storage is more than sufficient -- and one less service to manage.
Convex vs Supabase vs Firebase for React Native
Here's an honest comparison based on building production apps with all three:
| Feature | Convex | Supabase | Firebase |
|---------|:-:|:-:|:-:|
| Real-time reactivity | Built-in (automatic) | Manual subscriptions | Snapshot listeners |
| TypeScript support | Full, generated types | Generated separately | Partial, manual types |
| Auth providers | Google, Apple, Magic Link, Anonymous | 20+ providers | Google, Apple, email, phone |
| Database model | Document-based with schema | PostgreSQL (relational) | Document-based (Firestore) |
| Server functions | Actions, Mutations, Queries | Edge Functions (Deno) | Cloud Functions |
| Cold starts | Minimal | Noticeable on Edge Functions | Noticeable on Cloud Functions |
| File storage | Built-in CDN | Supabase Storage (S3-based) | Cloud Storage |
| Raw SQL access | No | Yes (full Postgres) | No |
| GraphQL | No | Yes (pg_graphql) | No |
| Self-hosting | No | Yes | No |
| Free tier | Generous for dev | Generous for small apps | Generous with limits |
| React Native SDK | Same as web SDK | Dedicated RN guide | Dedicated RN SDK |
| Pricing model | Usage-based | Usage + compute | Usage-based (complex) |
| Offline support | Limited | Limited | Good (Firestore) |
No single backend wins every category. Supabase wins on SQL access and self-hosting. Firebase wins on offline support. Convex wins on developer experience, type safety, and reactivity.
Is Convex a Good Backend?
Yes, for most React Native apps in 2026, Convex is an excellent backend choice. But "good" depends on what you're building.
Convex is a great backend when you need real-time features, type-safe server functions, and fast iteration speed. The developer experience is genuinely the best I've used -- npx convex dev watches your files, deploys changes instantly, and regenerates your typed client on every save. There's no waiting for deploys, no managing infrastructure, no debugging cold starts.
For AI-powered apps specifically, Convex actions are the simplest way I've found to integrate external APIs. You write a function, import the SDK you need, call the API, store the result, and the client updates automatically. The entire flow from server function to UI update is handled by the framework.
The pricing is reasonable for indie developers and small teams. The free tier covers development and early-stage apps comfortably. You only start paying meaningful amounts when you have meaningful usage.
The honest caveats: Convex is newer than Supabase and Firebase, so the community is smaller -- fewer Stack Overflow answers and fewer third-party tutorials (which is exactly why I'm writing this guide -- there are zero third-party Convex React Native guides as of March 2026). The team is responsive and the docs are excellent, but you'll sometimes be the first person to hit a specific edge case.
When NOT to Use Convex
Convex is not the right choice for every project. Here's when I'd recommend something else:
You need raw SQL access. If your app requires complex SQL queries, joins across many tables, or stored procedures, use Supabase. Convex's query language is powerful but it's not SQL. If your workflow revolves around Postgres tooling, switching to Convex means giving that up.
You need GraphQL. Convex doesn't offer a GraphQL API. If your frontend is built around Apollo or Relay, Supabase's pg_graphql extension or a custom GraphQL server is a better fit.
You're already deep in Supabase or Firebase. If you have a production app with hundreds of Edge Functions and a mature setup, migrating to Convex for marginal DX improvements doesn't make sense. The migration cost outweighs the benefits unless you're hitting real pain points.
You need strong offline-first support. If your app must work fully offline and sync when connectivity returns, Firebase Firestore has the most mature offline support. Convex's offline story is still evolving.
You need to self-host. Convex is managed-only. If regulatory requirements or company policy mandate self-hosted infrastructure, Supabase is the obvious choice.
How I Use Convex in Ship React Native
Ship React Native comes pre-configured with Convex handling three core systems: authentication, database, and all 8 AI functions.
Auth: Convex Auth is set up with Google Sign-In, Apple Sign-In, Magic Link, and Anonymous auth. The auth flow, token management, and session persistence are all wired up and working on both iOS and Android. You configure your OAuth credentials and you're done.
Database: The schema is defined, indexes are set up, and the reactive queries are integrated throughout the app. User profiles, generation history, subscription status -- all stored in Convex and automatically synced to the UI.
AI Functions: This is where Convex made the biggest difference. All 8 AI integrations -- text generation, image generation, text-to-speech, speech-to-text, vision analysis, translation, summarization, and chat -- run as Convex actions. Each function calls the appropriate AI API (OpenAI, ElevenLabs, etc.), stores the result in the database, and the client sees the update immediately through reactive queries.
The total backend code for all of this lives in the convex/ directory. No separate deployment pipeline, no Edge Function configuration, no webhook endpoints to maintain. When I need to add a new AI function or modify an existing one, I edit a file in convex/, save it, and npx convex dev deploys it in seconds.
If you're starting a new React Native app and want Convex already configured with auth, database, and AI integrations, Ship React Native gets you there without the setup weeks. But even if you're building from scratch, Convex with Expo is straightforward enough that you can have a reactive backend running in an afternoon. The combination of type safety, built-in reactivity, and a unified SDK for auth, database, and server functions makes it the backend I reach for on every new React Native project.
Related posts:
- How to Build an AI-Powered React Native App in 2026 -- the AI integration patterns that run on Convex
- React Native In-App Purchases: RevenueCat vs expo-iap -- monetizing the app you build with Convex
- React Native Templates in 2026 -- comparing templates that use different backends