BetterConvex
Best-in-class stack
tRPC + Drizzle + TanStack Query + Better Auth
What's included
cRPC
tRPC, ported to Convex. Real-time, AI-ready.
ORM
Drizzle v1-style schema, relations, queries, and mutations for Convex. Performance guardrails built in.
TanStack Query native
useQuery, useMutation, useInfiniteQuery. All the patterns you know, with real-time updates.
HTTP Router
Switch from real-time to HTTP with no extra setup. Same API, both sides.
Auth
Better Auth integration with session management and lifecycle hooks.
RSC
Fire-and-forget prefetch or awaited preloading. Hydration included.
Plugins
Cross-cutting features that span schema and API. Inspired by Better Auth.
Aggregates
O(log n) counts, sums, min/max, and ranked leaderboards. Zero-scan, index-backed.
Migrations
Versioned, resumable data migrations with up/down, dry-run, and drift detection.
Simple to use
It's quick and easy to build a typesafe API with Better Convex.
Define your schema
Drizzle-style tables, columns, indexes, and relations. All in one file.
// convex/functions/schema.ts
import { convexTable, defineRelations, text, boolean, id, index } from 'better-convex/orm';
export const users = convexTable('users', {
name: text().notNull(),
email: text().notNull(),
});
export const posts = convexTable('posts', {
title: text().notNull(),
published: boolean(),
userId: id('users').notNull(),
}, (t) => [index('by_user').on(t.userId)]);
export const relations = defineRelations({ users, posts }, (r) => ({
users: { posts: r.many.posts() },
posts: {
author: r.one.users({ from: r.posts.userId, to: r.users.id }),
},
}));Define your procedures
Query with the ORM — relations, filtering, ordering, pagination. All type-safe.
// convex/functions/posts.ts
export const list = publicQuery
.input(z.object({ limit: z.number().optional() }))
.query(async ({ ctx, input }) => {
return ctx.orm.query.posts.findMany({
where: { published: true },
orderBy: { createdAt: 'desc' },
limit: input.limit ?? 10,
with: { author: true },
});
});Create your client
Bridge Convex with TanStack Query. The useCRPC hook gives you a typed proxy to all your procedures.
// src/lib/convex/crpc.tsx
import { api } from '@convex/api';
import { createCRPCContext } from 'better-convex/react';
export const { useCRPC } = createCRPCContext({ api });
Connect and start querying!
Full TypeScript autocompletion from server to client. Queries subscribe to real-time updates via WebSocket.
// app/page.tsx
const crpc = useCRPC();
const { data } = useQuery(
crpc.posts.list.queryOptions({ limit: 10 })
);
// Mutations work the same way
const create = useMutation(crpc.posts.create.mutationOptions());
create.mutate({ title: 'Hello' });