A zero-overhead React Query integration for Hono RPC clients with full type safety.
- 🔒 Full Type Safety - Preserves all Hono RPC types end-to-end
- 🚀 Zero Runtime Overhead - Minimal proxy-based implementation
- ⚡ React Query Integration - Generate
queryOptions,infiniteQueryOptions, andmutationOptions - 🎯 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
npm install hono-rq @tanstack/react-query honopnpm add hono-rq @tanstack/react-query honoyarn add hono-rq @tanstack/react-query honobun add hono-rq @tanstack/react-query honoimport { 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);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>
);
}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 withhc<AppType>()
Returns:
- Extended client with React Query methods added to each endpoint
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
}
);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);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);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 typeconst 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);
}
});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>
);
}- Node.js: 18+
- React: 18+
- @tanstack/react-query: 5.0+
- hono: 4.0+
- TypeScript: 5.0+ (recommended)
We welcome contributions! Please see our Contributing Guide for details.
MIT © Peter Ferguson
- Hono - Ultrafast web framework for the Edges
- TanStack Query - Powerful data synchronization for web applications
- tRPC - End-to-end typesafe APIs made easy