npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

hono-query-rpc

v0.1.4

Published

tRPC-style TanStack Query integration for Hono RPC

Readme

hono-query-rpc

A utility library that brings tRPC-like developer experience when using Hono RPC with TanStack Query.

Uses TanStack Query's queryOptions / mutationOptions pattern as-is.

const { data } = useQuery(
  api.users.$get.queryOptions({ query: { page: "1" } }),
);

const create = useMutation(api.users.$post.mutationOptions({}));

Key features

  • Auto-generated query functionsqueryOptions() and mutationOptions() are derived directly from the Hono RPC client type, so there is no boilerplate to write.
  • Query key managementqueryKey() returns a stable, type-safe key for every endpoint. Use it for cache invalidation without hardcoding strings.
  • Auto Idempotency-Key injection — mutation requests automatically receive a unique Idempotency-Key header (refreshed after each successful mutation) to prevent accidental duplicate writes. Disable with autoIdempotency: false.

Installation

# npm
npm install hono-query-rpc

# pnpm
pnpm add hono-query-rpc

# bun
bun add hono-query-rpc

Peer dependencies

bun add hono @tanstack/react-query react

Quick Start

// lib/api.ts
import { hc } from "hono/client";
import { createHonoQuery } from "hono-query-rpc";
import type { AppType } from "@/server";

const client = hc<AppType>("/");

export const api = createHonoQuery(client);
// components/UserList.tsx
import { useQuery } from "@tanstack/react-query";
import { api } from "@/lib/api";

function UserList() {
  const { data, isLoading } = useQuery(api.api.users.$get.queryOptions({}));

  if (isLoading) {
    return <p>Loading...</p>;
  }

  return (
    <ul>
      {data?.map((u) => (
        <li key={u.id}>{u.name}</li>
      ))}
    </ul>
  );
}

API

createHonoQuery(client, options?)

Creates a proxy client that wraps a Hono RPC client with TanStack Query integration.

| Option | Type | Default | Description | | ----------------- | ------------------------------------------------ | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | defaultHeaders | HeadersFactory | { "content-type": "application/json", "accept": "application/json" } | Headers merged into every request. Accepts a static object, a Headers instance, an entry tuple array, or a (async) getter function. Providing this option replaces the default headers entirely. | | autoIdempotency | boolean | true | Automatically adds an Idempotency-Key header to mutation requests. The key is refreshed after each successful mutation. | | parseResponse | (res: Response) => unknown \| Promise<unknown> | Throws HTTPError on !res.ok, otherwise returns res.json() | Customize how responses are parsed and errors are thrown. |

.queryOptions(input, options?)

Returns a TanStack Query queryOptions object. Pass undefined as input when the endpoint takes no parameters.

// With input
useQuery(api.api.users.$get.queryOptions({ query: { page: "1" } }));

// Without input
useQuery(api.api.users.$get.queryOptions());

// With TanStack Query options
useQuery(api.api.users.$get.queryOptions({}, { enabled: false }));

// With per-call hono headers
useQuery(
  api.api.users.$get.queryOptions(
    {},
    {
      hono: { headers: { "x-trace-id": crypto.randomUUID() } },
    },
  ),
);

.mutationOptions(options?)

Returns a TanStack Query mutationOptions object.

// Basic
useMutation(api.api.users.$post.mutationOptions({}));

// With TanStack Query callbacks
useMutation(
  api.api.users.$post.mutationOptions({
    onSuccess: () => queryClient.invalidateQueries(...),
    onError: (err) => console.error(err),
  }),
);

// With per-call hono headers
useMutation(
  api.api.users.$post.mutationOptions({
    hono: { headers: { "x-custom": "value" } },
  }),
);

.queryKey(input?)

Returns the query key for manual cache operations.

queryClient.invalidateQueries({ queryKey: api.users.$get.queryKey() });
queryClient.invalidateQueries({
  queryKey: api.users.$get.queryKey({ query: { page: "1" } }),
});

Header Management

Headers are managed in two layers with the following priority (call level overrides factory level):

Factory level — applied to all requests

// Static
const api = createHonoQuery(client, {
  defaultHeaders: { "x-app-id": "my-app" },
});

// Dynamic (evaluated on every request)
const api = createHonoQuery(client, {
  defaultHeaders: () => ({
    authorization: `Bearer ${useAuthStore.getState().token}`,
  }),
});

// Async dynamic
const api = createHonoQuery(client, {
  defaultHeaders: async () => ({
    authorization: `Bearer ${await refreshTokenIfNeeded()}`,
  }),
});

// Auto idempotency key for mutation requests (default: true, disable with false)
const api = createHonoQuery(client, {
  autoIdempotency: false,
});

Call level — applied to a specific request only

// queryOptions
useQuery(
  api.users.$get.queryOptions(input, {
    hono: { headers: { "x-trace-id": crypto.randomUUID() } },
  }),
);

// mutationOptions
useMutation(
  api.users.$post.mutationOptions({
    hono: { headers: { "x-custom": "value" } },
  }),
);

Custom Response Parsing

The default behavior is equivalent to:

import { HTTPError } from "hono-query-rpc";

// default parseResponse
(res) => {
  if (!res.ok) {
    throw new HTTPError(res);
  }
  return res.json();
};

You can override this with parseResponse:

import { createHonoQuery } from "hono-query-rpc";

const api = createHonoQuery(client, {
  parseResponse: async (res) => {
    if (!res.ok) {
      const body = await res.json();
      throw new MyAppError(res.status, body.message);
    }
    return res.json();
  },
});

HTTPError

The default error class thrown on non-OK responses.

import { HTTPError } from "hono-query-rpc";

// err.status    — HTTP status code (e.g. 404)
// err.statusText — HTTP status text (e.g. "Not Found")
// err.response  — original Response object

Example

A minimal blog app (Next.js 15 + Hono + TanStack Query) is included in the example/ directory.

# 1. Build the library (from repo root)
bun run build

# 2. Install example dependencies
cd example
bun install

# 3. Start dev server
bun run dev

Open http://localhost:3000.

What the example demonstrates

| Feature | Where | | ------------------------- | --------------------------------------------------------- | | queryOptions() | app/page.tsx — fetch all posts | | queryOptions({ param }) | app/posts/[id]/page.tsx — fetch single post | | mutationOptions() | components/CreatePostModal.tsx — create post | | mutationOptions() | app/page.tsx + app/posts/[id]/page.tsx — delete post | | queryKey() invalidation | after create/delete mutations | | HTTPError handling | 404 on post detail page | | Error testing panel | app/page.tsx — trigger query/mutation errors in-browser | | TanStack Query DevTools | bottom-right corner of the example app |


Development

Prerequisites

  • Nix — reproducible dev environment
  • direnv — automatically activates the dev environment
# Install dependencies
bun install

# Build
bun run build

# Test
bun test

# Test (watch mode)
bun test --watch

# Type check
bun run typecheck

License

MIT