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

query-routing

v2.2.7

Published

A CLI tool that generates a complete API structure for React or Vue 3 with TanStack Query, Axios interceptors, and full TypeScript support. Detects your framework, then scaffolds typed query, mutation, infinite-query, lazy-query and cache-control hooks. Z

Downloads

243

Readme

query-routing

License: MIT npm version TypeScript

A zero-configuration CLI that generates a production-ready, strongly-typed API layer for your React or Vue 3 application.

Stop writing boilerplate. query-routing scaffolds a complete architectural foundation for your API calls, combining Axios, TanStack Query and TypeScript into a scalable, modular structure — and it detects which framework you are on before it writes a single file.


✨ Features

  • ⚡️ Instant Scaffolding: A full api/ directory in seconds.
  • ⚛️ / 💚 React and Vue 3: Detects your framework from package.json and generates the matching hooks — @tanstack/react-query or @tanstack/vue-query.
  • 📄 OpenAPI/Swagger Integration: Detects your spec (openapi.yaml, openapi.yml, openapi.json) and auto-generates routes and types.
  • 🧠 Intelligent Detection: Finds your src/ folder and places files accordingly.
  • 📦 Dependency Management: Checks for the packages your framework needs and offers to install them.
  • 🛡️ Strong Typing: Every hook derives its payload and response types from one route contract. Endpoint names autocomplete; wrong ones fail to compile.
  • 🔌 Interceptors Ready: Pre-configured Axios instance with request/response interceptors.
  • 🪝 Five Hooks, Not One: query, mutation, infinite query, lazy query and typed cache control.

🚀 Quick Start

No global install needed. Run it inside your existing project with whichever package manager you use:

npx query-routing

# pnpm
pnpm dlx query-routing

# yarn
yarn dlx query-routing

# bun
bunx query-routing

Framework detection

The CLI reads your package.json and picks the target automatically:

| What it finds | What it generates | | ----------------------- | ----------------- | | @tanstack/react-query | React hooks | | @tanstack/vue-query | Vue 3 composables | | react (and no vue) | React hooks | | vue (and no react) | Vue 3 composables | | both, or neither | it asks you |

The TanStack adapter wins over the framework itself, because a repo can legitimately carry both react and vue while querying with only one of them.

🔥 For Super Fast Generation (OpenAPI)

To auto-generate types and routes from your backend:

  1. Name your spec file exactly one of:
  • openapi.yaml
  • openapi.yml
  • openapi.json
  1. Place it in the root of your project.
  2. Run the command!

What happens next?

  1. The CLI detects your framework and checks package.json for dependencies.
  2. It asks to install missing packages (Axios, TanStack Query) if needed.
  3. It detects your openapi file and parses your endpoints.
  4. It generates a structured api folder in your project root or src/api (if src exists).

Command-line options

Everything is interactive by default. The flags exist for CI, for scripted regeneration, and for the cases where you want to override a decision.

| Flag | What it does | | ---------------------- | ------------------------------------------------------------------------- | | -y, --yes | Accept every prompt. Required for non-interactive use. | | --framework <name> | Force react or vue instead of detecting from package.json. | | --strict | Fail the run if any $ref cannot be resolved, instead of emitting any. | | --force | Overwrite developer-owned files too | | --no-unwrap-envelope | Keep a response envelope your spec describes . | | --no-openapi | Ignore any spec that is present and scaffold the starter template only. | | --no-install | Never install missing dependencies; warn and carry on. | | -h, --help | Print usage. |

# Regenerate types in CI, and fail the build if the spec has holes in it
npx query-routing --yes --strict

📂 The Generated Structure

Identical for both frameworks — only the contents of hook/ differ:

src/api/
├── 📂 axios-config/
│   └── interceptor.ts       # Centralized Axios instance & error handling
├── 📂 enum/
│   └── api-path.ts          # String constants for API endpoints
├── 📂 hook/
│   ├── hook.ts              # The five hooks + the public re-exports
│   ├── 📂 core/
│   │   ├── keys.ts          # Hierarchical cache-key factory
│   │   ├── request.ts       # Framework-free apiRequest + error helpers
│   │   └── locale.ts        # Optional per-locale cache scoping
│   └── 📂 types/
│       └── hook.types.ts    # Options & return types, derived from your routes
├── 📂 req/
│   └── req.types.ts         # Request payload interfaces
├── 📂 res/
│   └── res.types.ts         # Response payload interfaces
├── 📂 types/
│   ├── api.types.ts         # Generic Response Wrappers (IAxiosData)
│   └── route.type.ts        # The contract definitions for your routes
└── api-route.ts             # The runtime implementation of the routes

Everything is plain TypeScript in your repo. Edit it, rename it, delete what you do not use — there is no runtime dependency on this package after generation.


🛠 The Hooks

All five import from api/hook/hook.

1. useQueryWithAxios — read and cache

React

const { data, isPending, error } = useQueryWithAxios("auth", "getUserInfo");

// narrow the cached value; the result stays fully typed
const { data: names } = useQueryWithAxios("auth", "getUserInfo", undefined, {
  select: (res) => res.data.map((user) => user.name),
});

Vue 3 — the payload may be a plain value, a ref, a reactive object or a getter. Whatever you pass is unwrapped for both the cache key and the request, so the query refetches by itself when a filter changes.

const filters = reactive({ pageNumber: 1, pageSize: 10 });
const { data, isPending } = useQueryWithAxios("posts", "getPosts", filters);

Options beyond TanStack's own:

  • localeAware (default true) — scope the cache entry per locale.
  • keepPreviousData (default false) — hold the previous rows while the next page loads. Off by default because holding data keeps status on "success", which makes isPending false forever and hides any skeleton gated on it.

2. useMutationWithAxios — write

const { mutateAsync: createUser, isPending } = useMutationWithAxios(
  "auth",
  "createUser",
  { invalidates: ["auth", ["posts", "getPosts"]] },
);

invalidates takes a route name for a whole group, or a [route, method] tuple for one endpoint. The screens showing that data refresh themselves — no caller has to remember to refetch().

3. useInfiniteQueryWithAxios — paginated lists

Only endpoints that actually page are accepted: the payload must extend IPaginationPayload and the response must wrap an array. Anything else is not even offered by autocomplete.

React

const { items, total, isEmpty, sentinelRef, isFetchingNextPage } =
  useInfiniteQueryWithAxios("posts", "getPosts", {
    filters: { authorId },
    pageSize: 20,
  });

return (
  <>
    <ul>
      {items.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
    <div ref={sentinelRef} />
  </>
);

Vue 3

const { items, isEmpty, loadMore, onInfiniteScroll, sentinelRef } =
  useInfiniteQueryWithAxios("posts", "getPosts", {
    filters: () => ({ authorId: authorId.value }),
    pageSize: 20,
  });
<!-- Quasar -->
<q-infinite-scroll @load="onInfiniteScroll">
  <li v-for="post in items" :key="post.id" v-text="post.title" />
</q-infinite-scroll>

<!-- anything else -->
<li v-for="post in items" :key="post.id" v-text="post.title" />
<div ref="sentinelRef" />

Returned on top of TanStack's own fields:

| Field | What it is | | ------------------ | ---------------------------------------------------------------- | | items | every page flattened into one typed list | | total | server's count when present, else the number of loaded rows | | isEmpty | true only once loading finished and there is genuinely nothing | | loadMore | fetch the next page, ignored while one is in flight | | sentinelRef | attach to a bottom element to auto-load on scroll | | onInfiniteScroll | Vue only — drop-in @load handler for Quasar |

The default getNextPageParam stops on the server's count when present, and otherwise falls back to "a short page means the last page". Override it for endpoints that report neither.

4. useLazyQueryWithAxios — fetch on demand, still cached

For data fetched in response to a user action: opening a dropdown, clicking "show result".

const {
  fetch: loadUsers,
  isFetching,
  data,
} = useLazyQueryWithAxios("auth", "getUserInfo");

const users = await loadUsers();

Doing this with a mutation works, but a mutation is uncached and un-deduplicated by design, so opening the same dropdown five times fires five identical GETs. Here the second call is free while the entry is fresh. Use refresh() to bypass the cache.

5. useApiCache — typed cache control

Nothing outside api/hook/ has to hand-build a query key.

const cache = useApiCache();

await cache.invalidate("posts"); // the whole group
await cache.invalidate("posts", "getPosts"); // one endpoint, every page
await cache.prefetch("posts", "getPosts"); // warm before navigating
cache.setData("auth", "getUserInfo", undefined, (previous) => previous);
cache.clear(); // the one call for logout()

Also available: refresh, remove, cancel, getData, and queryClient as an escape hatch.

Bonus: calling the API outside a component

apiRequest is framework-free — no React, no Vue — so it works in router guards, Pinia/Redux actions, event handlers and tests, with the same types and the same validation the hooks use.

import { apiRequest, getApiErrorMessage } from "@/api/hook/hook";

const users = await apiRequest("auth", "getUserInfo");

getApiErrorMessage(error), getApiErrorStatus(error) and isApiError(error) ship alongside it.


🌍 Optional: per-locale caching

A localized API returns different rows for ar and en-US, so the same request in two languages is really two different resources. Register a resolver once and every cache key picks up the locale — switching language then refetches on its own.

React

import i18n from "i18next";
import { setApiLocaleResolver } from "./api/hook/hook";

setApiLocaleResolver(() => i18n.language);

Vue 3

import i18n from "@/i18n";
import { setApiLocaleResolver } from "@/api/hook/hook";

setApiLocaleResolver(() => i18n.global.locale.value);

Skip this entirely and nothing changes: the default resolver returns null and the locale segment of every key is simply null. Opt out per query with localeAware: false.


🧩 Workflow: Adding a New Route

  1. Define types — request params in req/req.types.ts, response shape in res/res.types.ts.
  2. Define the contract — map the method name to those types in types/route.type.ts.
  3. Implement — add the Axios call in api-route.ts using ApiPath constants.

The hooks need no registration step; they read the contract.

// 1. types/route.type.ts
export interface IGetPostsPayload extends IPaginationPayload {
  authorId: number;
}

export interface IPostRoute {
  getPosts: (payload: IGetPostsPayload) => IResponse<IPost[]>;
}

// 2. api-route.ts
export const ApiRoute: IAxiosRoute = {
  posts: {
    getPosts: (payload) => api.get(ApiPath.POSTS, { params: payload }),
  },
};

Extending IPaginationPayload is what makes getPosts eligible for useInfiniteQueryWithAxios.


⚙️ Configuration

Interceptors & Auth

src/api/axios-config/interceptor.ts is where you inject tokens:

api.interceptors.request.use((config) => {
  const token = localStorage.getItem("token");
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

Base URL

The generated interceptor ships with a placeholder base URL. Point it at your own API — reading it from the environment is the usual choice:

// Vite
const baseUrl = import.meta.env.VITE_API_URL;

// Create React App / Node
const baseUrl = process.env.REACT_APP_API_URL;

When you generate from an OpenAPI spec with servers entries, the CLI offers to use one of those instead.

Retries

Read hooks retry transient failures only — 408, 425, 429, 500, 502, 503, 504 and network/timeout errors — and give up after 2 attempts. TanStack's default would also retry 401s and 404s, which can never succeed and only delay the failure. Adjust RETRYABLE_STATUS and MAX_RETRIES in hook/core/request.ts.


⬆️ Upgrading from 1.x

Version 2 is a breaking change to the cache-key format.

| 1.x | 2.x | | ------------------------------------------- | ---------------------------------------------- | | ["posts-getPosts", payload] | ["posts", "getPosts", kind, locale, payload] | | useQueryWithAxios, useMutationWithAxios | those two plus infinite, lazy and cache hooks | | api/hook/hook.ts only | api/hook/ with core/ and types/ | | React only | React and Vue 3 |

What this buys you is partial invalidation: with a tuple, any prefix is a valid filter, so ["posts"] drops a whole group and ["posts", "getPosts"] drops one endpoint across every locale and page. A flat string key can never express that.

To upgrade: back up any edits you made inside api/hook/, delete the folder, and re-run npx query-routing. Call sites of useQueryWithAxios and useMutationWithAxios keep working unchanged; only hand-written queryKey arrays and direct queryClient calls need updating to go through useApiCache.


⚖️ License

MIT License

Copyright (c) 2025 Abdelhadi Alkayal

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


💌 Feedback & Support

Questions, support or suggestions: [email protected]