Skip to content

peterferguson/hono-rq

Repository files navigation

hono-rq

npm version MIT License TypeScript

A zero-overhead React Query integration for Hono RPC clients with full type safety.

Features

  • 🔒 Full Type Safety - Preserves all Hono RPC types end-to-end
  • 🚀 Zero Runtime Overhead - Minimal proxy-based implementation
  • React Query Integration - Generate queryOptions, infiniteQueryOptions, and mutationOptions
  • 🎯 Smart Query Keys - Automatic, stable query key generation based on endpoint and parameters
  • 🌟 Developer Experience - IntelliSense support with autocomplete for all endpoints
  • 🧪 Well Tested - Comprehensive test coverage including browser and integration tests
  • 📦 Tree Shakable - Only bundle what you use

Installation

npm install hono-rq @tanstack/react-query hono
pnpm add hono-rq @tanstack/react-query hono
yarn add hono-rq @tanstack/react-query hono
bun add hono-rq @tanstack/react-query hono

Quick Start

1. Extend your Hono client

import { extend } from 'hono-rq';
import { hc } from 'hono/client';
import type { AppType } from './server'; // Your Hono app type

const client = hc<AppType>('http://localhost:3000');
const extendedClient = extend(client);

2. Use with React Query

import { useQuery, useMutation } from '@tanstack/react-query';

function UsersList() {
  // Generate query options for GET requests
  const queryOptions = extendedClient.users.$get.queryOptions();
  const { data, isLoading, error } = useQuery(queryOptions);

  // Generate mutation options for POST requests  
  const mutationOptions = extendedClient.users.$post.mutationOptions();
  const createUser = useMutation(mutationOptions);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      {data?.users.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
      <button
        onClick={() => createUser.mutate({ 
          json: { name: 'New User', email: 'user@example.com' }
        })}
      >
        Add User
      </button>
    </div>
  );
}

API Reference

extend(client)

Extends a Hono RPC client with React Query integration methods.

import { extend } from 'hono-rq';

const extendedClient = extend(client);

Parameters:

  • client - Any Hono RPC client created with hc<AppType>()

Returns:

  • Extended client with React Query methods added to each endpoint

Query Options

Generate React Query options for GET endpoints:

// Basic usage
const queryOptions = extendedClient.users.$get.queryOptions();

// With parameters
const queryOptions = extendedClient.users[':id'].$get.queryOptions({
  param: { id: '123' }
});

// With query parameters
const queryOptions = extendedClient.users.$get.queryOptions({
  query: { limit: '10', offset: '0' }
});

// With React Query options
const queryOptions = extendedClient.users.$get.queryOptions(
  { query: { limit: '10' } },
  { 
    enabled: true,
    staleTime: 5000,
    retry: 3 
  }
);

Infinite Query Options

Generate options for paginated data with React Query's infinite queries:

const infiniteQueryOptions = extendedClient.posts.$get.infiniteQueryOptions(
  (pageParam) => ({
    query: { 
      page: pageParam?.toString() ?? '1',
      limit: '10' 
    }
  }),
  {
    initialPageParam: 1,
    getNextPageParam: (lastPage, pages) => 
      lastPage.hasMore ? pages.length + 1 : undefined,
  }
);

const { 
  data, 
  fetchNextPage, 
  hasNextPage, 
  isLoading 
} = useInfiniteQuery(infiniteQueryOptions);

Mutation Options

Generate options for POST, PUT, PATCH, DELETE endpoints:

// Basic mutation
const mutationOptions = extendedClient.users.$post.mutationOptions();

// With React Query options
const mutationOptions = extendedClient.users[':id'].$patch.mutationOptions(
  undefined,
  {
    onSuccess: (data) => {
      console.log('User updated:', data);
    },
    onError: (error) => {
      console.error('Update failed:', error);
    }
  }
);

const mutation = useMutation(mutationOptions);

TypeScript

hono-rq is built with TypeScript and provides full type safety:

// All types are preserved from your Hono app
type UsersResponse = InferResponseType<typeof extendedClient.users.$get>;
type CreateUserRequest = InferRequestType<typeof extendedClient.users.$post>;

// Query options have correct types
const queryOptions = extendedClient.users.$get.queryOptions();
// queryOptions.queryFn return type matches your Hono endpoint

// Mutations are fully typed  
const mutation = useMutation(
  extendedClient.users.$post.mutationOptions()
);
// mutation.mutate() expects the correct request type

Advanced Usage

Error Handling

const queryOptions = extendedClient.users.$get.queryOptions(undefined, {
  retry: (failureCount, error) => {
    // Custom retry logic
    if (error.status === 404) return false;
    return failureCount < 3;
  },
  onError: (error) => {
    console.error('Query failed:', error);
  }
});

Examples

Complete CRUD Example

import { extend } from 'hono-rq';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

const extendedClient = extend(client);

function UserManager() {
  const queryClient = useQueryClient();

  // List users
  const usersQuery = useQuery(
    extendedClient.users.$get.queryOptions()
  );

  // Get single user
  const userQuery = useQuery(
    extendedClient.users[':id'].$get.queryOptions({
      param: { id: selectedUserId }
    }, {
      enabled: !!selectedUserId
    })
  );

  // Create user
  const createUser = useMutation(
    extendedClient.users.$post.mutationOptions(undefined, {
      onSuccess: () => {
        queryClient.invalidateQueries({ 
          queryKey: extendedClient.users.$get.queryOptions().queryKey 
        });
      }
    })
  );

  // Update user
  const updateUser = useMutation(
    extendedClient.users[':id'].$patch.mutationOptions(undefined, {
      onSuccess: () => {
        queryClient.invalidateQueries({ queryKey: ['users'] });
      }
    })
  );

  // Delete user
  const deleteUser = useMutation(
    extendedClient.users[':id'].$delete.mutationOptions(undefined, {
      onSuccess: () => {
        queryClient.invalidateQueries({ queryKey: ['users'] });
      }
    })
  );

  return (
    <div>
      {/* Your UI here */}
    </div>
  );
}

Requirements

  • Node.js: 18+
  • React: 18+
  • @tanstack/react-query: 5.0+
  • hono: 4.0+
  • TypeScript: 5.0+ (recommended)

Contributing

We welcome contributions! Please see our Contributing Guide for details.

License

MIT © Peter Ferguson

Related Projects

  • Hono - Ultrafast web framework for the Edges
  • TanStack Query - Powerful data synchronization for web applications
  • tRPC - End-to-end typesafe APIs made easy