Client Package
@veloxts/client provides a type-safe client for calling VeloxTS APIs from React applications.
Installation
Section titled “Installation”pnpm add @veloxts/client @tanstack/react-queryimport { createVeloxHooks, VeloxProvider } from '@veloxts/client/react';import type { AppRouter } from '@/api';
// Create typed hooks with tRPC-style APIexport const api = createVeloxHooks<AppRouter>();
// app/providers.tsximport { QueryClient, QueryClientProvider } from '@tanstack/react-query';import { VeloxProvider } from '@veloxts/client/react';import type { AppRouter } from '@/api';
const queryClient = new QueryClient();
export function Providers({ children }: { children: React.ReactNode }) { return ( <QueryClientProvider client={queryClient}> <VeloxProvider<AppRouter> config={{ baseUrl: '/api' }}> {children} </VeloxProvider> </QueryClientProvider> );}import { createClient } from '@veloxts/client';import type { userProcedures } from '@/api/procedures/users';
type AppProcedures = { users: typeof userProcedures;};
export const api = createClient<AppProcedures>({ baseUrl: '/api',});
// Direct usage (no React Query)const users = await api.users.listUsers();const user = await api.users.getUser({ id: '123' });React Hooks (Recommended)
Section titled “React Hooks (Recommended)”With createVeloxHooks, you get tRPC-style hooks with full autocomplete:
useQuery
Section titled “useQuery”import { api } from '@/lib/api';
function UserList() { // Full autocomplete: api.users.listUsers.useQuery() const { data: users, isLoading, error } = api.users.listUsers.useQuery();
if (isLoading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>;
return ( <ul> {users?.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> );}useMutation
Section titled “useMutation”import { api } from '@/lib/api';
function CreateUserForm() { const { mutate, isPending } = api.users.createUser.useMutation({ onSuccess: () => { // Invalidate the users list to refetch api.users.listUsers.invalidate(); }, });
return ( <form onSubmit={(e) => { e.preventDefault(); const formData = new FormData(e.currentTarget); mutate({ name: formData.get('name') as string, email: formData.get('email') as string, }); }}> <input name="name" required /> <input name="email" type="email" required /> <button disabled={isPending}> {isPending ? 'Creating...' : 'Create User'} </button> </form> );}Query with Parameters
Section titled “Query with Parameters”function UserProfile({ userId }: { userId: string }) { const { data: user } = api.users.getUser.useQuery({ id: userId });
return user ? <h1>{user.name}</h1> : null;}Optimistic Updates
Section titled “Optimistic Updates”function DeleteUserButton({ userId }: { userId: string }) { const queryClient = useQueryClient();
const { mutate } = api.users.deleteUser.useMutation({ onMutate: async ({ id }) => { // Cancel outgoing refetches await queryClient.cancelQueries({ queryKey: ['users', 'listUsers'] });
// Snapshot previous value const previous = queryClient.getQueryData(['users', 'listUsers']);
// Optimistically remove user queryClient.setQueryData(['users', 'listUsers'], (old: User[]) => old?.filter(u => u.id !== id) );
return { previous }; }, onError: (err, variables, context) => { // Rollback on error queryClient.setQueryData(['users', 'listUsers'], context?.previous); }, onSettled: () => { // Refetch after mutation api.users.listUsers.invalidate(); }, });
return <button onClick={() => mutate({ id: userId })}>Delete</button>;}Error Handling
Section titled “Error Handling”import { isVeloxClientError } from '@veloxts/client';
try { await api.users.getUser({ id: 'invalid' });} catch (error) { if (isVeloxClientError(error)) { switch (error.code) { case 'NOT_FOUND': console.log('User not found'); break; case 'VALIDATION_ERROR': console.log('Invalid input:', error.details); break; case 'UNAUTHORIZED': // Redirect to login break; } }}Type Inference
Section titled “Type Inference”Types flow automatically from backend to frontend:
// Backend defines the procedureexport const userProcedures = procedures('users', { getUser: procedure() .input(z.object({ id: z.string().uuid() })) .output(UserSchema) .query(handler),});
// Frontend gets full type safety// Input is typed: { id: string }// Output is typed: Userconst user = await api.users.getUser({ id: '123' });// ^? User
// TypeScript catches errorsawait api.users.getUser({ id: 123 }); // Error: Expected stringNext Steps
Section titled “Next Steps”- tRPC Bridge - Server-side procedure calls
- Procedures - Define backend endpoints
- Validation - Input/output schemas