BetterConvex

Best-in-class stack

tRPC + Drizzle + TanStack Query + Better Auth

Simple to use

It's quick and easy to build a typesafe API with Better Convex.

1

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 }),
  },
}));
2

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 }) => {
input: { limit?: number }
return ctx.orm.query.posts.findMany({ where: { published: true }, orderBy: { createdAt: 'desc' }, limit: input.limit ?? 10, with: { author: true }, }); });
3

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 });
▶ posts▶ users
4

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(
const data: Post[] | undefined
crpc.posts.list.queryOptions({ limit: 10 })
limit?: number
); // Mutations work the same way const create = useMutation(crpc.posts.create.mutationOptions()); create.mutate({ title: 'Hello' });