Better Convex is a modern Next.js starter template that combines Convex backend with Better Auth integration to create a robust, type-safe development experience.
The template replaces raw Convex queries and mutations with custom type-safe wrappers that include built-in authentication, rate limiting, and role-based access control.
You get GitHub and Google OAuth providers configured with Better Auth, alongside React Query and Jotai for state management.
It is great for SaaS applications, social platforms, productivity tools, and any project requiring secure user management with real-time data synchronization.
Features
🔐 OAuth Authentication: Pre-configured GitHub and Google OAuth with Better Auth adapter for Convex.
🛡️ Type-Safe Backend: Custom function wrappers replacing raw Convex queries and mutations with full TypeScript support.
⚡ Rate Limiting: Built-in rate limiting with configurable tiers based on user roles.
👥 Role-Based Access: Automatic role checking and permission management in backend functions.
🔄 Optimistic Updates: Pre-configured React Query setup with optimistic UI updates.
📊 Database Relations: Convex Ents integration for type-safe entity relationships.
🎨 Modern UI: Tailwind CSS with shadcn/ui components for rapid prototyping.
📱 App Router: Next.js with App Router and Server Components support.
🔍 Form Validation: React Hook Form with Zod validation for client and server.
🚀 Developer Tools: Pre-configured helpers, hooks, and development commands.
Use Cases
- SaaS Applications: Build subscription-based software with user authentication, role management, and usage tracking.
- Social Platforms: Create community-driven applications with user profiles, comments, and real-time interactions.
- Productivity Tools: Develop task management, note-taking, or collaboration tools with user data persistence.
- E-commerce Backends: Implement user accounts, order management, and inventory tracking systems.
- Content Management: Build blogging platforms, documentation sites, or knowledge bases with user-generated content.
Basic Usage
1. Clone the repository from GitHub and install the necessary dependencies using pnpm.
git clone https://github.com/udecode/better-convex.git
cd better-convex
pnpm install2. You must configure environment variables for both Next.js and Convex. Copy the example files to create your local configuration.
For Next.js, create a .env.local file:
cp .env.example .env.localFor Convex, create a .env file inside the convex directory:
cp convex/.env.example convex/.env3. Add your GitHub and Google OAuth client IDs and secrets to the .env.local file. These are required for the authentication system to function.
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret4. Run the dev command to start the Next.js frontend and the Convex backend simultaneously.
pnpm dev5. If this is your first time setting up the project, you need to initialize the Convex environment in a separate terminal.
pnpm dev:initYour application will be available at http://localhost:3005.
Creating Backend Functions
Replace traditional Convex functions with type-safe wrappers. Create public queries for data that doesn’t require authentication:
export const getPublicTodos = createPublicQuery()({
args: { limit: z.number().optional() },
returns: z.array(z.object({
_id: zid("todos"),
title: z.string(),
completed: z.boolean()
})),
handler: async (ctx, args) => {
return await ctx.table("todos")
.filter(q => q.eq(q.field("public"), true))
.take(args.limit ?? 10);
},
});For authenticated operations, use auth-protected functions with automatic user context:
export const createTodo = createAuthMutation({
rateLimit: "todo/create",
})({
args: { title: z.string().min(1).max(200) },
returns: zid("todos"),
handler: async ({ user, table }, args) => {
return await table("todos").insert({
title: args.title,
completed: false,
userId: user._id,
});
},
});Frontend Integration
Use custom hooks instead of raw useQuery calls. These hooks provide better error handling and loading states:
function TodoList() {
const { data: todos, isPending } = useAuthQuery(api.todos.getUserTodos, {});
const createTodo = useAuthMutation(api.todos.create);
const handleSubmit = (data) => {
toast.promise(createTodo.mutateAsync(data), {
loading: "Creating todo...",
success: "Todo created!",
error: (e) => e.data?.message ?? "Failed to create todo",
});
};
if (isPending) return <LoadingSpinner />;
return (
<div>
{todos?.map(todo => (
<TodoItem key={todo._id} todo={todo} />
))}
</div>
);
}Database Schema Definition
Define your database schema using Convex Ents for type-safe relationships:
const schema = defineEntSchema({
users: defineEnt({
name: v.optional(v.string()),
email: v.string(),
role: v.union(v.literal("USER"), v.literal("ADMIN")),
}).field("emailVerified", v.boolean(), { default: false }),
todos: defineEnt({
title: v.string(),
completed: v.boolean(),
userId: v.id("users"),
}).index("by_user", ["userId"]),
});Rate Limiting Configuration
Configure rate limits in the rate limiter helper file:
export const rateLimiter = new RateLimiter(components.rateLimiter, {
'todo/create:free': { kind: 'fixed window', period: MINUTE, rate: 5 },
'todo/create:premium': { kind: 'fixed window', period: MINUTE, rate: 20 },
});The system automatically selects the appropriate tier based on user roles when you specify rateLimit: "todo/create" in your functions.
Server Components Integration
Use server-side helpers for data fetching in React Server Components:
export default async function ProfilePage() {
const user = await getSessionUser();
if (!user) redirect('/login');
const profile = await fetchAuthQuery(api.users.getProfile, {});
return (
<div>
<h1>Welcome, {profile.name}</h1>
<ProfileForm initialData={profile} />
</div>
);
}Related Resources
- Convex Documentation: The official documentation for the Convex backend framework. https://docs.convex.dev
- Convex Better Auth Documentation: Specific documentation for the Better Auth integration with Convex. https://convex-better-auth.netlify.app/
- Better Auth Documentation: General documentation for the Better Auth framework. https://better-auth.com
- Next.js Documentation: The official guide and documentation for the Next.js framework. https://nextjs.org/docs
FAQs
Q: Why use custom function wrappers instead of raw Convex queries?
A: The custom wrappers provide built-in authentication, rate limiting, type safety, and error handling. They reduce boilerplate code and prevent common security issues by enforcing consistent patterns across your application.
Q: Can I use this template with other authentication providers?
A: Yes, Better Auth supports multiple providers including Discord, Twitter, and custom providers. You can add additional providers by configuring them in the auth setup file and adding the required environment variables.
Q: How does the rate limiting work with different user tiers?
A: The system automatically selects rate limits based on user roles. When you specify a rate limit key like “todo/create”, it appends the user’s role to find the appropriate limit configuration, such as “todo/create:free” or “todo/create:premium”.
Q: Is the database schema migration handled automatically?
A: Convex handles schema migrations automatically when you deploy changes. During development, schema changes are applied instantly. The Ents system provides additional type safety on top of Convex’s native schema system.
Q: Can I deploy this to production easily?
A: Yes, the template includes production-ready configurations. Deploy the Next.js app to Vercel and the Convex backend deploys automatically. You’ll need to configure production OAuth credentials and environment variables.






