BETTER-CONVEX

Server Side Calls

Call procedures from server-side code without HTTP overhead.

This guide covers how to call cRPC procedures from server-side code. You'll learn to set up the caller factory, configure authentication, and invoke procedures directly without HTTP overhead.

Overview

FeatureBenefit
Direct CallsNo HTTP roundtrip, direct function invocation
Type SafetyFull TypeScript inference for inputs and outputs
Auth IntegrationBuilt-in token management with Better Auth
Unified SyntaxSame ctx.caller.x.y() pattern as client-side

Let's explore the setup and usage patterns.

The caller lets you invoke cRPC procedures directly from server-side code. No HTTP roundtrip - direct function calls with full type safety.

Environment Setup

The caller requires the Convex site URL (for HTTP actions), which is different from the real-time URL.

Note: The site URL uses port 3211 locally and .site domain in production.

.env.local
# WebSocket API (port 3210)
NEXT_PUBLIC_CONVEX_URL=http://localhost:3210

# HTTP routes (port 3211)
NEXT_PUBLIC_CONVEX_SITE_URL=http://localhost:3211
.env.local
# Generated by Convex
NEXT_PUBLIC_CONVEX_URL=https://your-project.convex.cloud

# Add manually - replace .cloud with .site
NEXT_PUBLIC_CONVEX_SITE_URL=https://your-project.convex.site

Create Caller

Without Auth

src/lib/convex/server.ts
import { api } from '@convex/api';
import { meta } from '@convex/meta';
import { createCallerFactory } from 'better-convex/server';

export const { createContext, createCaller } = createCallerFactory({
  api,
  convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL!,
  meta,
});

Better Auth (Next.js)

For Better Auth with Next.js, see Next.js Setup which uses convexBetterAuth from better-convex/auth-nextjs.

Custom Auth

Use createCallerFactory with a custom getToken function.

src/lib/convex/server.ts
import { api } from '@convex/api';
import { meta } from '@convex/meta';
import { createCallerFactory } from 'better-convex/server';

export const { createContext, createCaller } = createCallerFactory({
  api,
  convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL!,
  getToken: async (siteUrl, headers) => {
    // Extract token from headers (implement your auth logic)
    const token = headers.get('authorization')?.replace('Bearer ', '');
    return { token };
  },
  meta,
});

Note: The meta parameter is generated via CLI. See Metadata for setup.

Usage

Create context from headers, then use ctx.caller.

src/app/api/users/route.ts
import { createContext } from '@/lib/convex/server';

// Create context from request headers
const ctx = await createContext({ headers: request.headers });

// Query
const users = await ctx.caller.user.list({});

// Query with input
const user = await ctx.caller.user.getById({ id: userId });

// Mutation
const newId = await ctx.caller.user.create({
  name: 'John',
  email: 'john@example.com',
});

// Check auth state
if (!ctx.isAuthenticated) {
  redirect('/login');
}

Hono Middleware

src/middleware.ts
const ctx = await createContext({ headers: c.req.raw.headers });
const user = await ctx.caller.user.getSessionUser({});

API Routes

src/app/api/data/route.ts
const ctx = await createContext({ headers: request.headers });
const data = await ctx.caller.user.list({});

Migrate from Convex

What stays the same

  • Server-side data fetching concept
  • Full type safety for procedure calls

What's new

Before (vanilla Convex):

src/app/api/users/route.ts
import { fetchQuery, fetchMutation } from 'convex/nextjs';
import { api } from '@convex/_generated/api';

const users = await fetchQuery(api.user.list, {});
const id = await fetchMutation(api.user.create, { name: 'John', email: 'john@example.com' });

After (cRPC):

src/app/api/users/route.ts
import { createContext } from '@/lib/convex/server';

const ctx = await createContext({ headers });
const users = await ctx.caller.user.list({});
const id = await ctx.caller.user.create({ name: 'John', email: 'john@example.com' });

Key differences:

  • Unified proxy syntax matching client-side usage
  • ctx.isAuthenticated for auth state
  • createCallerFactory for reusable context creation
  • Built-in auth/token management

API Reference

createCallerFactory

Creates a caller factory with token management and retry logic.

types.ts
type CreateCallerFactoryOptions<TApi> = {
  api: TApi;
  convexSiteUrl: string;
  auth?: {
    getToken: (
      siteUrl: string,
      headers: Headers,
      opts?: unknown
    ) => Promise<{ token?: string; isFresh?: boolean }>;
    isUnauthorized?: (error: unknown) => boolean;
  };
  meta: CallerMeta;
};

Returns { createContext, createCaller }:

types.ts
type ConvexContext<TApi> = {
  token: string | undefined;
  isAuthenticated: boolean;
  caller: ServerCaller<TApi>;
};

// createContext: (opts: { headers: Headers }) => Promise<ConvexContext<TApi>>
// createCaller: (ctxFn: () => Promise<ConvexContext<TApi>>) => LazyCaller<TApi>

Next Steps

On this page