Skip to content

Hybrid Architecture

Use this architecture when you have both TypeScript and non-TypeScript clients.

  • Mobile apps (REST) + web dashboard (tRPC)
  • Public API (REST) + internal tools (tRPC)
  • Gradual migration from REST to tRPC
  • Different teams with different needs

VeloxTS generates both REST and tRPC endpoints from the same procedures:

export const orderProcedures = procedures('orders', {
// Available as:
// - REST: GET /api/orders
// - tRPC: trpc.orders.listOrders.query()
listOrders: procedure()
.output(z.array(OrderSchema))
.query(({ ctx }) => ctx.db.order.findMany()),
});
// iOS Swift
let url = URL(string: "http://api.example.com/api/orders")!
let (data, _) = try await URLSession.shared.data(from: url)
let orders = try JSONDecoder().decode([Order].self, from: data)
// React with tRPC
const { data: orders } = trpc.orders.listOrders.useQuery();
Terminal window
curl https://api.example.com/api/orders \
-H "Authorization: Bearer ${API_KEY}"

You can expose different procedures to different transports:

// Internal only - tRPC
export const internalProcedures = procedures('internal', {
getMetrics: procedure()
.query(...)
.rest(false), // Disable REST
});
// Public only - REST
export const publicProcedures = procedures('public', {
getStatus: procedure()
.query(...)
// REST enabled by default
});

The same guards work for both:

const getOrder = procedure()
.guard(authenticated)
.input(z.object({ id: z.string() }))
.query(...)
// REST: Authorization header
// tRPC: Same header or cookie

For REST clients that need versioning:

export const v1Procedures = procedures('v1/orders', {
listOrders: procedure()...
});
export const v2Procedures = procedures('v2/orders', {
listOrders: procedure()... // New shape
});