Skip to content

Client Usage ​

vRPC provides a high-fidelity type-safe interface for your API. Because it is zero-runtime, the way you initialize your client determines how the compiler optimizes your code.

πŸ—οΈ Initializing the Client ​

The vRPCClient is the entry point for your API. You typically create it in a file like src/vrpc/client.ts.

Basic Setup ​

typescript
import { vRPCClient, type vRPCResponse, type vRPCResponseBundle } from 'vrpc'
import fetcher from './rpc.mutator'

export const api = new vRPCClient({
  baseUrl: 'https://api.example.com',
  fetcher,
}).proxy as unknown as TypedClient<RpcOperations, vRPCResponse<vRPCResponseBundle>>

Using Environment Variables ​

In modern environments, you should never hardcode your API URL. vRPC works seamlessly with bundler-provided environment variables.

Vite / Astro ​

typescript
baseUrl: import.meta.env.VITE_API_URL || '/'

Next.js / Webpack ​

typescript
baseUrl: process.env.NEXT_PUBLIC_API_URL || '/'

πŸ› οΈ Configuration Options ​

OptionTypeDescription
baseUrlstringThe prefix for all API calls (e.g. /api/v1).
fetcherFetcherYour Custom Mutator.
serializersRecord<string, Serializer>Custom Body Serializers.
meta() => Promise<any>(Dev only) The lazy-loaded metadata map.

The Core Contract: ResponsePackage ​

By default, vRPC operations deliver a Response Package. This structure is the "unit of work" for a single HTTP response and is the key to automatic type narrowing.

typescript
interface ResponsePackage<T> {
  status: number // The key used for type narrowing
  data: T // The typed body for this specific status
  headers: Headers
  contentType: string
}

Making Requests ​

Every generated method accepts an options object for parameters and configuration:

typescript
try {
  const response = await api.createItem({
    params: { id: '123' }, // Path params (/items/:id)
    query: { force: true }, // Query params (?force=true)
    body: { name: 'New Item' }, // Request body
  })

  // Handle successful response
  if (response.status === 201) {
    console.log('Created:', response.data)
  }
} catch (error) {
  // Handle network or API errors
  console.error('API Error:', error)
}

Handling Responses ​

The way you interact with the result depends on how your Mutator is configured.

1. Default (Minimalist) ​

In the default scaffolded setup, the fetcher returns the ResponsePackage directly. You narrow the status code immediately:

typescript
const response = await api.getUser({ params: { id: '1' } })

if (response.status === 200) {
  // response.data is narrowed to 'User'
  console.log(response.data.name)
}

2. Custom Wrappers ​

If you choose to wrap your responses (e.g., in a Result class), your interaction will reflect that structure:

typescript
const result = await api.getUser({ params: { id: '1' } })

if (result.success) {
  const pkg = result.success
  if (pkg.status === 200) {
    console.log(pkg.data.name)
  }
}

Type Narrowing Power ​

The magic of vRPC is that the data field is a discriminated union tied to the status. This is why we recommend keeping the status field prominentβ€”it is the direct link to your API's models.

TIP

You can pass standard fetch options (signals, cache, etc.) via the options property in any call.

Released under the MIT License.