Skip to content

tRPC API Architecture

The tRPC architecture skips REST endpoint generation entirely and exposes your procedures as type-safe RPC calls. Your frontend gets full autocompletion, compile-time error checking, and zero runtime overhead from schema serialization. Choose this when every client is TypeScript and you don’t need REST or OpenAPI.

  • All clients are TypeScript
  • Maximum type safety is priority
  • Internal APIs between services
  • No need for REST/OpenAPI
Terminal window
npx create-velox-app my-api --trpc

With --trpc, Velox TS skips REST generation. You get pure tRPC:

src/procedures/users.ts
import { procedures, procedure, resourceSchema, resource, resourceCollection } from '@veloxts/velox';
import { z } from '@veloxts/velox';
// Define resource schema with field visibility
const UserSchema = resourceSchema()
.public('id', z.string())
.public('name', z.string())
.authenticated('email', z.string())
.admin('lastLoginAt', z.date().nullable())
.build();
export const userProcedures = procedures('users', {
list: procedure()
.query(async ({ ctx }) => {
const users = await ctx.db.user.findMany();
return resourceCollection(users, UserSchema.authenticated);
}),
get: procedure()
.input(z.object({ id: z.string() }))
.query(async ({ input, ctx }) => {
const user = await ctx.db.user.findUniqueOrThrow({
where: { id: input.id }
});
return resource(user, UserSchema.authenticated);
}),
create: procedure()
.input(CreateUserSchema)
.mutation(async ({ input, ctx }) => {
const user = await ctx.db.user.create({ data: input });
return resource(user, UserSchema.authenticated);
}),
});

Use @veloxts/client in tRPC mode for fully typed API calls:

frontend/src/api.ts
import { createClient } from '@veloxts/client';
import type { AppRouter } from '../../api/src/router';
export const api = createClient<AppRouter>({
baseUrl: 'http://localhost:3030/trpc',
// mode: 'trpc' - auto-detected when baseUrl ends with /trpc
});
// Fully typed!
const users = await api.users.list();
const user = await api.users.get({ id: '123' });

The client auto-detects tRPC mode when baseUrl ends with /trpc. Queries (procedures starting with get, list, find) use GET with encoded input, mutations use POST with JSON body.

For React apps, use the hooks API:

frontend/src/lib/api.ts
import { createVeloxHooks, VeloxProvider } from '@veloxts/client/react';
import type { AppRouter } from '@/api';
export const api = createVeloxHooks<AppRouter>();
// In your app
function UserList() {
const { data: users } = api.users.list.useQuery();
return <ul>{users?.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

See Client Package for full React integration.

For existing codebases or specific tRPC features, use TRPCRouter to bridge types:

import { createTRPCReact } from '@trpc/react-query';
import type { TRPCRouter } from '@veloxts/router';
import type { AppRouter } from '../../api/src/router';
export const trpc = createTRPCReact<TRPCRouter<AppRouter>>();

For most Velox TS apps, @veloxts/client is recommended.

Types flow automatically:

// Backend defines the shape
const createUser = procedure()
.input(z.object({
name: z.string(),
email: z.string().email(),
}))
.mutation(...)
// Frontend gets the types
trpc.users.create.mutate({
name: 'Alice', // TypeScript knows this is required
email: 'alice@example.com',
// @ts-error - 'age' doesn't exist
age: 30,
});

If you later need REST endpoints (for mobile apps, third parties, webhooks), add the REST adapter to your app:

src/index.ts
import { veloxApp, rest } from '@veloxts/velox';
import { collections } from './router.js';
const app = await veloxApp({ port: 3030 });
app.routes(
rest(collections, { prefix: '/api' }) // Add this line
);

REST uses naming conventions to infer HTTP methods, so you may need to rename procedures:

// Before (tRPC-style, short names)
list: procedure()...
get: procedure()...
create: procedure()...
// After (REST-style, includes resource name)
listUsers: procedure()... // → GET /api/users
getUser: procedure()... // → GET /api/users/:id
createUser: procedure()... // → POST /api/users

See REST Conventions for the full naming guide.