Hybrid Architecture
The hybrid architecture generates both REST and tRPC endpoints from the same procedure definitions. Your mobile team gets a standard REST API with OpenAPI docs, while your web dashboard talks directly over tRPC with full type safety. No code duplication, no separate API layers — one procedure serves both transports.
Different Teams, Different Transports
Section titled “Different Teams, Different Transports”- Mobile apps (REST) + web dashboard (tRPC)
- Public API (REST) + internal tools (tRPC)
- Gradual migration from tRPC to REST
- Different teams with different needs
Hybrid for the backend
Section titled “Hybrid for the backend”Velox TS generates both REST and tRPC endpoints from the same procedures:
import { resourceSchema, resourceCollection } from '@veloxts/router';
const OrderSchema = resourceSchema() .public('id', z.string()) .public('total', z.number()) .authenticated('userId', z.string()) .admin('paymentDetails', z.object({ /* ... */ })) .build();
export const orderProcedures = procedures('orders', { // Available as: // - REST: GET /api/orders // - tRPC: trpc.orders.listOrders.query() listOrders: procedure() .query(async ({ ctx }) => { const orders = await ctx.db.order.findMany(); return resourceCollection(orders, OrderSchema.authenticated); }),});Hybrid for the frontend
Section titled “Hybrid for the frontend”Mobile App (REST)
Section titled “Mobile App (REST)”// iOS Swiftlet 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)Web Dashboard (tRPC)
Section titled “Web Dashboard (tRPC)”// React with tRPCconst { data: orders } = trpc.orders.listOrders.useQuery();Third-Party Integration (REST)
Section titled “Third-Party Integration (REST)”curl https://api.example.com/api/orders \ -H "Authorization: Bearer ${API_KEY}"Selective Exposure
Section titled “Selective Exposure”Procedures with recognized naming convention prefixes (e.g., get*, list*, create*) generate both tRPC and REST endpoints. Procedures without a recognized prefix are tRPC-only:
// tRPC + REST (naming convention matches)export const publicProcedures = procedures('status', { getStatus: procedure() .query(...), // → tRPC: trpc.status.getStatus // → REST: GET /api/status/:id});
// tRPC-only (no naming convention match)export const internalProcedures = procedures('internal', { syncMetrics: procedure() .query(...), // → tRPC: trpc.internal.syncMetrics // → No REST endpoint generated});Authentication Across Transports
Section titled “Authentication Across Transports”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 cookieAPI Versioning
Section titled “API Versioning”For REST clients that need versioning:
export const v1Procedures = procedures('v1/orders', { listOrders: procedure()...});
export const v2Procedures = procedures('v2/orders', { listOrders: procedure()... // New shape});Related Content
Section titled “Related Content”- REST Conventions - REST patterns
- tRPC Adapter - tRPC configuration
- OpenAPI - API documentation