Xavier
Xavier2w ago

How to handle consistency for client-side only APIs?

Hi, we are using tRPC as a BFF (Backend for Frontend) which act as a proxy between ou UI and our dozen of backend services. What's the recommended approach to handle direct client-side API calls when they cannot be implemented with tRPC due to legacy limitations? Should we use React Query directly, or is there a way to extend tRPC's router in the client-side to maintain consistent API handling? PS: For better understanding, we'd like to add client-side procedures because we need to calls APIs that can only be called in a browser
Solution:
```ts export function buildTrpcClientFromRouter<AppRouter extends AnyRouter>(router: AppRouter) { function mockLink<TRouter extends AnyRouter>(): TRPCLink<TRouter> { return () => {...
Jump to solution
8 Replies
Arduano
Arduano2w ago
are you basically trying to run a trpc server instance in the browser? with a trpc client connecting directly to the same instance in the same page? Oh nvm I misunderstood, disregard
cannot be implemented with tRPC due to legacy limitations
if it's because of the backend not supporting trpc, then react query is probably the best way to go
Xavier
XavierOP2w ago
We cannot add procedures/mutations on the client-side by extending the router in any way?
Arduano
Arduano2w ago
"extending the router"? you can make a separate router probably, it's possible to make a router that calls a virtual server-side that's still running in your browser
Xavier
XavierOP2w ago
That would do the trick yes, any example in mind?
Arduano
Arduano2w ago
here:
Solution
Arduano
Arduano2w ago
export function buildTrpcClientFromRouter<AppRouter extends AnyRouter>(router: AppRouter) {
function mockLink<TRouter extends AnyRouter>(): TRPCLink<TRouter> {
return () => {
return ({ op }) => {
return observable((observer) => {
const handler = async () => {
const result = await callTRPCProcedure({
procedures: appRouter._def.procedures,
ctx: {},
path: op.path,
input: op.input,
getRawInput: async () => op.input,
type: op.type,
signal: op.signal,
});

const isIterableResult = isAsyncIterable(result) || isObservable(result);

if (op.type !== 'subscription') {
if (isIterableResult) {
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'Cannot return an async iterable in a non-subscription call',
});
}
observer.next({
result: {
type: 'data',
data: result,
},
context: op.context,
});
observer.complete();
} else {
if (!isIterableResult) {
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'Cannot return a non-async iterable in a subscription call',
});
}

const iterable = isObservable(result) ? observableToAsyncIterable(result, op.signal) : result;
for await (const item of iterable) {
if (op.signal?.aborted) {
break;
}

observer.next({
result: {
type: 'data',
data: item,
},
context: op.context,
});
}
observer.complete();
}
};

void handler();

return () => {
observer.complete();
};
});
};
};
}

const trpcClient = createTRPCClient<AppRouter>({
links: [mockLink()],
});

return trpcClient;
}

const t = initTRPC.create({ isServer: true });

const appRouter = t.router({
hello: t.procedure.query(() => 'hi'),
});

const trpcClient = buildTrpcClientFromRouter(appRouter);
export function buildTrpcClientFromRouter<AppRouter extends AnyRouter>(router: AppRouter) {
function mockLink<TRouter extends AnyRouter>(): TRPCLink<TRouter> {
return () => {
return ({ op }) => {
return observable((observer) => {
const handler = async () => {
const result = await callTRPCProcedure({
procedures: appRouter._def.procedures,
ctx: {},
path: op.path,
input: op.input,
getRawInput: async () => op.input,
type: op.type,
signal: op.signal,
});

const isIterableResult = isAsyncIterable(result) || isObservable(result);

if (op.type !== 'subscription') {
if (isIterableResult) {
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'Cannot return an async iterable in a non-subscription call',
});
}
observer.next({
result: {
type: 'data',
data: result,
},
context: op.context,
});
observer.complete();
} else {
if (!isIterableResult) {
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'Cannot return a non-async iterable in a subscription call',
});
}

const iterable = isObservable(result) ? observableToAsyncIterable(result, op.signal) : result;
for await (const item of iterable) {
if (op.signal?.aborted) {
break;
}

observer.next({
result: {
type: 'data',
data: item,
},
context: op.context,
});
}
observer.complete();
}
};

void handler();

return () => {
observer.complete();
};
});
};
};
}

const trpcClient = createTRPCClient<AppRouter>({
links: [mockLink()],
});

return trpcClient;
}

const t = initTRPC.create({ isServer: true });

const appRouter = t.router({
hello: t.procedure.query(() => 'hi'),
});

const trpcClient = buildTrpcClientFromRouter(appRouter);
Arduano
Arduano2w ago
@Xavier though I wrote this for trpc v11, you'd have to adjust it for v10
Xavier
XavierOP2w ago
Thank you very much for your help, that looks perfect 🙏

Did you find this page helpful?