hono-query-client
v1.0.10
Published
A type-safe, React Query-powered client library for Hono applications with automatic hook generation and intelligent cache management
Maintainers
Readme
hono-query-client
A type-safe, React Query-powered client library for Hono applications. Automatically generates React hooks for your Hono API routes with full TypeScript support, intelligent cache management, and method-specific mutations.
Features
- 🔥 Type-Safe: Full TypeScript support with automatic type inference from your Hono app
- ⚡ Method-Specific Mutations: Dedicated hooks for POST, PUT, PATCH, and DELETE operations
- 🔄 Intelligent Cache Management: Automatic cache invalidation based on endpoint patterns
- 📦 Zero Configuration: Works out of the box with your existing Hono setup
- 🎯 Optimistic Updates: Built-in support for optimistic mutations
- 🌊 Infinite Queries: Native support for paginated data with infinite scrolling
- 🛡️ Error Handling: Comprehensive error handling with structured error responses
- 🎣 React Query Integration: Built on top of TanStack React Query for robust state management
Installation
npm install hono-query-client hono @tanstack/react-query
# or
yarn add hono-query-client hono @tanstack/react-query
# or
pnpm add hono-query-client hono @tanstack/react-query
# or
bun add hono-query-client hono @tanstack/react-queryNote: @tanstack/react-query is a peer dependency and must be installed. The library provides a default QueryClient instance, so you don't need to manually configure it unless you want custom settings.
Quick Start
1. Create API Client and Provider
// lib/api.ts
import { hc } from 'hono/client';
import { createHonoQueryProxy, HonoQueryProvider } from 'hono-query-client';
import type { AppType } from './server'; // Your Hono app type
const honoClient = hc<AppType>('http://localhost:3000');
export const api = createHonoQueryProxy(honoClient);
// Export the provider with the client pre-configured
export const AppProvider = ({ children }: { children: React.ReactNode }) => (
<HonoQueryProvider client={honoClient}>
{children}
</HonoQueryProvider>
);Note: The HonoQueryProvider automatically creates a default QueryClient and includes the TanStack Query QueryClientProvider, so you don't need to manually create a query client or wrap your app with both providers.
Optional: You can still provide your own QueryClient if you need custom configuration:
import { QueryClient } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
},
},
});
export const AppProvider = ({ children }: { children: React.ReactNode }) => (
<HonoQueryProvider queryClient={queryClient} client={honoClient}>
{children}
</HonoQueryProvider>
);2. Set up your App
// App.tsx
import { AppProvider } from './lib/api';
function App() {
return (
<AppProvider>
<YourApp />
</AppProvider>
);
}3. Use in Components
import { api } from './lib/api';
function UserProfile({ userId }: { userId: string }) {
// Query data
const { data: user, isLoading, error } = api.users[':id'].useQuery(
{ param: { id: userId } },
{ enabled: !!userId }
);
// Method-specific mutations
const updateUser = api.users[':id'].patch.useMutation({
onSuccess: () => {
console.log('User updated successfully!');
}
});
const deleteUser = api.users[':id'].delete.useMutation();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h1>{user?.name}</h1>
<button
onClick={() => updateUser.mutate({
param: { id: userId },
json: { name: 'New Name' }
})}
>
Update User
</button>
<button onClick={() => deleteUser.mutate({ param: { id: userId } })}>
Delete User
</button>
</div>
);
}Core Concepts
Query Hooks
Query hooks are automatically generated for any endpoint that supports GET requests:
// Basic query
const { data, isLoading, error } = api.users.useQuery();
// Query with parameters
const { data: user } = api.users[':id'].useQuery({
param: { id: '123' }
});
// Query with query parameters
const { data: posts } = api.posts.useQuery({
query: { page: 1, limit: 10 }
});Method-Specific Mutations
Instead of a generic useMutation, hono-query provides method-specific hooks that automatically handle the correct HTTP method:
// POST - Create new resource
const createPost = api.posts.post.useMutation();
createPost.mutate({ json: { title: 'New Post', content: '...' } });
// PATCH - Update existing resource
const updatePost = api.posts[':id'].patch.useMutation();
updatePost.mutate({
param: { id: '123' },
json: { title: 'Updated Title' }
});
// DELETE - Remove resource
const deletePost = api.posts[':id'].delete.useMutation();
deletePost.mutate({ param: { id: '123' } });
// PUT - Replace resource
const replacePost = api.posts[':id'].put.useMutation();
replacePost.mutate({
param: { id: '123' },
json: { title: 'New Title', content: 'New Content' }
});Infinite Queries
For paginated data with infinite scrolling:
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = api.posts.useInfiniteQuery(
{ query: { limit: 10 } },
{
getNextPageParam: (lastPage) => lastPage.nextCursor,
}
);Error Handling
The library provides structured error handling:
import { HonoQueryError } from 'hono-query-client';
const mutation = api.users.post.useMutation({
onError: (error) => {
if (error instanceof HonoQueryError) {
console.log('Status:', error.response.status);
console.log('Error data:', error.data);
}
}
});Cache Management
Automatic cache invalidation happens based on endpoint patterns:
- Collection mutations (
POST /users) invalidate the collection cache (/users) - Resource mutations (
PATCH /users/:id) invalidate both the resource (/users/:id) and collection (/users)
You can also manually manage cache:
import { useHonoQueryContext } from 'hono-query-client';
function MyComponent() {
const { utils } = useHonoQueryContext();
// Manual cache invalidation
const handleRefresh = () => {
utils.users.invalidate();
};
}Advanced Usage
Type Inference
The library automatically infers types from your Hono app:
import type { AppType } from './server';
// All these types are automatically inferred
type UserResponse = ApiOutputs<AppType>['users'][':id']; // GET response
type UserInput = ApiInputs<AppType>['users'][':id']; // GET input
type CreateUserInput = ApiMutationInputs<AppType>['users']; // POST input
type CreateUserOutput = ApiMutationOutputs<AppType>['users']; // POST outputCustom Headers and Configuration
const honoClient = hc<AppType>('http://localhost:3000', {
headers: {
'Authorization': 'Bearer token',
'X-Custom-Header': 'value'
},
// Add dynamic headers
headers: () => ({
'Authorization': `Bearer ${localStorage.getItem('token')}`,
})
});Optimistic Updates
const updateUser = api.users[':id'].patch.useMutation({
onMutate: async (variables) => {
// Cancel outgoing refetches
await queryClient.cancelQueries({ queryKey: ['users', variables.param.id] });
// Snapshot previous value
const previousUser = queryClient.getQueryData(['users', variables.param.id]);
// Optimistically update
queryClient.setQueryData(['users', variables.param.id], {
...previousUser,
...variables.json
});
return { previousUser };
},
onError: (err, variables, context) => {
// Rollback on error
queryClient.setQueryData(
['users', variables.param.id],
context?.previousUser
);
},
});API Reference
Core Functions
createHonoQueryProxy<TApp>(client)- Creates the main API proxyHonoQueryProvider- React context provider for the query clientuseHonoQueryContext()- Hook to access query client and utils
Generated Hook Patterns
api.endpoint.useQuery(input?, options?)- Query hook for GET requestsapi.endpoint.useInfiniteQuery(input, options)- Infinite query hookapi.endpoint.post.useMutation(options?)- POST mutation hookapi.endpoint.put.useMutation(options?)- PUT mutation hookapi.endpoint.patch.useMutation(options?)- PATCH mutation hookapi.endpoint.delete.useMutation(options?)- DELETE mutation hook
Type Exports
HonoClient<TApp>- The raw Hono client typeClientQueryOutput<TApp, ...>- GET response typesClientQueryInput<TApp, ...>- GET input typesClientMutationOutput<TApp, ...>- Mutation response typesClientMutationInput<TApp, ...>- Mutation input typesGetQueryOutput<TClient, ...>- Advanced GET response typeGetQueryInput<TClient, ...>- Advanced GET input typeGetMutationOutput<TClient, ...>- Advanced mutation response typeGetMutationInput<TClient, ...>- Advanced mutation input typeHonoQueryError- Error class for failed requests
For a much better developer experience, see the Advanced Type-Safety Patterns guide to avoid passing TApp in every type.
Examples
See the examples directory for complete working examples:
Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
MIT License - see the LICENSE file for details.
