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
| Feature | Benefit |
|---|---|
| Direct Calls | No HTTP roundtrip, direct function invocation |
| Type Safety | Full TypeScript inference for inputs and outputs |
| Auth Integration | Built-in token management with Better Auth |
| Unified Syntax | Same 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.
# WebSocket API (port 3210)
NEXT_PUBLIC_CONVEX_URL=http://localhost:3210
# HTTP routes (port 3211)
NEXT_PUBLIC_CONVEX_SITE_URL=http://localhost:3211# 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.siteCreate Caller
Without Auth
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.
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.
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
const ctx = await createContext({ headers: c.req.raw.headers });
const user = await ctx.caller.user.getSessionUser({});API Routes
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):
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):
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.isAuthenticatedfor auth statecreateCallerFactoryfor reusable context creation- Built-in auth/token management
API Reference
createCallerFactory
Creates a caller factory with token management and retry logic.
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 }:
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>