Client & RPC

RPC Client

Patterns for calling API routes from the client.

RPC Client

The recommended pattern for calling API routes from client-side code is using standard fetch or Hono RPC for type-safety.

Fetch Pattern

Basic Fetch

const res = await fetch('/api/users');
const data = await res.json();

With Type Safety

Share types between your API and client code:

app/api/users/index.ts
import { Hono } from 'hono';

const app = new Hono();
app.get('/', (c) => c.json({ users: [] }));
export type AppType = typeof app;
lib/client.ts
import type { AppType } from '../api/users';
import { hc } from 'hono/client';

export const client = hc<AppType>('/api');

Helper Function Pattern

Create a reusable API client helper:

lib/api.ts
export async function apiFetch<T>(
  endpoint: string, 
  options?: RequestInit
): Promise<T> {
  const res = await fetch(`/api${endpoint}`, {
    ...options,
    headers: {
      'Content-Type': 'application/json',
      ...options?.headers,
    },
  });
  
  if (!res.ok) {
    const error = await res.json().catch(() => ({ message: 'Request failed' }));
    throw new Error(error.message);
  }
  
  return res.json();
}

Usage Examples

interface User {
  id: string;
  name: string;
}

// Get all users
const users = await apiFetch<User[]>('/users');

// Get single user
const user = await apiFetch<User>('/users/123');

// Create user
const newUser = await apiFetch<User>('/users', {
  method: 'POST',
  body: JSON.stringify({ name: 'Alice' }),
});

Error Handling

lib/api.ts
async function safeApiFetch<T>(endpoint: string): Promise<T | null> {
  try {
    const res = await fetch(`/api${endpoint}`);
    
    if (res.status === 404) {
      return null;
    }
    
    if (!res.ok) {
      const error = await res.json().catch(() => ({ message: 'Unknown error' }));
      throw new Error(error.message);
    }
    
    return res.json();
  } catch (error) {
    console.error('API call failed:', error);
    return null;
  }
}

Type Sharing Flow

Initializing diagram...

See Also

On this page