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

@nestjs-rpc/query

v1.0.1

Published

<div align="center">

Readme

@nestjs-rpc/query

Type-safe React Query hooks for NestRPC. Seamlessly integrate your RPC endpoints with TanStack Query for powerful caching, synchronization, and state management.

npm version npm downloads License: MIT TypeScript

InstallationQuick StartExamples


📖 📚 Full Documentation → 📖

Complete guides, API reference, and advanced examples


🎯 Why @nestjs-rpc/query?

Stop managing API state manually. This package gives you:

  • 🔒 End-to-End Type Safety - Full TypeScript inference from RPC methods to React hooks
  • Automatic Caching - Built on TanStack Query with intelligent cache management
  • 🔄 Auto Invalidation - Automatically invalidate related queries after mutations
  • 🎯 Zero Boilerplate - No manual query key management or cache invalidation logic
  • 🧩 Factory Pattern - Create reusable hooks with default options
  • 📤 File Upload Support - Works seamlessly with RPC file uploads

The Traditional Way (Without @nestjs-rpc/query)

// ❌ Manual React Query setup with no type safety
const { data } = useQuery({
  queryKey: ["user", userId],
  queryFn: async () => {
    const res = await fetch(`/api/user/${userId}`);
    return res.json(); // 😱 No types!
  },
});

// ❌ Manual cache invalidation
const mutation = useMutation({
  mutationFn: createUser,
  onSuccess: () => {
    queryClient.invalidateQueries(["users"]); // 😱 Easy to forget!
  },
});

The NestRPC Query Way

// ✅ Type-safe, automatic query key generation
const { data } = useRpcQuery(rpc.user.getUserById, { id: "123" });
//    ^? { id: string; name: string; email: string }
// Full autocomplete and type checking! 🎉

// ✅ Automatic cache invalidation
const mutation = useRpcMutation(rpc.user.createUser, {
  invalidate: [rpc.user.listUsers], // Auto-invalidates after success!
});

// ✅ Type-safe mutation calls with body wrapper
mutation.mutate({ body: { name: "John", email: "[email protected]" } });

📦 Installation

npm install @nestjs-rpc/query @nestjs-rpc/client @tanstack/react-query react
# or
pnpm add @nestjs-rpc/query @nestjs-rpc/client @tanstack/react-query react
# or
yarn add @nestjs-rpc/query @nestjs-rpc/client @tanstack/react-query react

Peer Dependencies:

  • @nestjs-rpc/client - The RPC client library
  • react - React 18.0.0 or higher
  • @tanstack/react-query - React Query 5.0.0 or higher

🚀 Quick Start

1. Setup QueryClientProvider

Wrap your app with QueryClientProvider:

// App.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { RpcClient } from '@nestjs-rpc/client';
import type { Manifest } from './path-to-your-manifest';

// Create your RPC client
const rpcClient = new RpcClient<Manifest>({
  baseUrl: 'http://localhost:3000',
});

// Create React Query client
const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      {/* Your app components */}
    </QueryClientProvider>
  );
}

2. Use RPC Queries

import { useRpcQuery } from '@nestjs-rpc/query';
import { rpc } from './rpc-client';

function UserList() {
  const { data, isLoading, error } = useRpcQuery(
    rpc.user.listUsers,
    undefined, // body (optional for methods with no body)
  );

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

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

3. Use RPC Mutations

import { useRpcMutation } from '@nestjs-rpc/query';
import { rpc } from './rpc-client';

function CreateUser() {
  const mutation = useRpcMutation(rpc.user.createUser, {
    invalidate: [rpc.user.listUsers], // Auto-invalidate after success
    onSuccess: (data) => {
      console.log('User created:', data);
    },
  });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    mutation.mutate({
      body: {
        name: 'John Doe',
        email: '[email protected]',
      },
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <button type="submit" disabled={mutation.isPending}>
        {mutation.isPending ? 'Creating...' : 'Create User'}
      </button>
    </form>
  );
}

That's it! You get:

  • ✅ Full TypeScript autocomplete
  • ✅ Automatic query key generation
  • ✅ Intelligent caching
  • ✅ Automatic cache invalidation

📚 Core API

useRpcQuery

Direct hook for one-off RPC queries. Automatically handles query key generation, caching, and type inference.

function useRpcQuery<TBody, TRet, TData = TRet>(
  method: RpcMethod<TBody, TRet> & { [PathSymbol]: string[] },
  body: TBody,
  options?: RpcQueryOptions<UseQueryOptions<TRet, AxiosError, TData, QueryKey>>,
  queryClient?: QueryClient,
): UseQueryResult<TData, AxiosError>;

Example:

// With required body
const { data } = useRpcQuery(
  rpc.user.getUserById,
  { id: "123" },
  {
    staleTime: 5000,
    enabled: !!userId,
  },
);

// With optional body (method accepts void/never/undefined)
const { data } = useRpcQuery(rpc.user.listUsers, undefined, {
  refetchInterval: 30000,
});

createRpcQuery

Factory function to create reusable query hooks with default options. Perfect for creating domain-specific hooks.

function createRpcQuery<TBody, TRet, TData = TRet>(
  method: RpcMethod<TBody, TRet> & { [PathSymbol]: string[] },
  defaultOptions?: RpcQueryOptions<UseQueryOptions<TRet, AxiosError, TData>>,
): RpcQueryHook<TBody, TRet, TData>;

Example:

// Create a reusable hook with defaults
const useUserList = createRpcQuery(rpc.user.listUsers, {
  staleTime: 60000,
  refetchOnWindowFocus: false,
});

// Use it in components
function UserList() {
  const { data } = useUserList(undefined);
  // ...
}

// Override options per usage
function AnotherComponent() {
  const { data } = useUserList(undefined, {
    refetchInterval: 10000, // Override default
  });
}

useRpcMutation

Direct hook for one-off RPC mutations. Supports automatic cache invalidation.

function useRpcMutation<TBody, TRet, TOnMutateResult = unknown>(
  method: RpcMethod<TBody, TRet> & { [PathSymbol]: string[] },
  options?: RpcMutationOptions<TBody, TRet, TOnMutateResult>,
): UseMutationResult<TRet, AxiosError, RpcMutationBody<TBody>, TOnMutateResult>;

Example:

const mutation = useRpcMutation(rpc.user.createUser, {
  invalidate: [rpc.user.listUsers],
  onSuccess: (data) => {
    toast.success("User created!");
  },
  onError: (error) => {
    toast.error(error.message);
  },
});

// Pass body directly in mutation variables
mutation.mutate({ body: { name: "John", email: "[email protected]" } });

createRpcMutation

Factory function to create reusable mutation hooks with default options.

function createRpcMutation<TBody, TRet, TOnMutateResult = unknown>(
  method: RpcMethod<TBody, TRet> & { [PathSymbol]: string[] },
  defaultOptions?: RpcMutationOptions<TBody, TRet, TOnMutateResult>,
): (
  options?: RpcMutationOptions<TBody, TRet, TOnMutateResultOverride>,
) => UseMutationResult<
  TRet,
  AxiosError,
  RpcMutationBody<TBody>,
  TOnMutateResultOverride
>;

Example:

// Create reusable mutation hook
const useCreateUser = createRpcMutation(rpc.user.createUser, {
  invalidate: [rpc.user.listUsers],
  onSuccess: () => {
    console.log('User created successfully');
  },
});

// Use in components
function CreateUserForm() {
  const createUser = useCreateUser({
    onSuccess: (data) => {
      // Additional per-component logic
      navigate(`/users/${data.id}`);
    },
  });

  return (
    <button onClick={() => createUser.mutate({ body: { name: 'John', email: '[email protected]' } })}>
      Create
    </button>
  );
}

useInvalidateRpcQuery

Hook that returns a function to invalidate queries by RPC route. Useful for manual cache invalidation.

function useInvalidateRpcQuery(): (
  rpcRoute: { [PathSymbol]: string[] },
  filters?: Omit<InvalidateQueryFilters<QueryKey>, "queryKey">,
  options?: InvalidateOptions,
) => Promise<void>;

Example:

function UserActions() {
  const invalidate = useInvalidateRpcQuery();

  const handleRefresh = () => {
    invalidate(rpc.user.listUsers);
  };

  return <button onClick={handleRefresh}>Refresh Users</button>;
}

🎨 Advanced Features

Additional Query Keys

Extend query keys for more granular cache control. Useful when you need to differentiate queries with the same body but different contexts.

// Using array
const { data } = useRpcQuery(
  rpc.user.getUserById,
  { id: "123" },
  {
    useAdditionalQueryKey: ["admin-view"], // Adds to query key
  },
);

// Using function (allows using hooks inside)
const { data } = useRpcQuery(
  rpc.user.getUserById,
  { id: "123" },
  {
    useAdditionalQueryKey: () => {
      const { userRole } = useAuth(); // Can use hooks!
      return [userRole];
    },
  },
);

// In factory defaults
const useUserById = createRpcQuery(rpc.user.getUserById, {
  useAdditionalQueryKey: ["default"],
});

// Instance-level keys are merged with factory defaults
const { data } = useUserById(
  { id: "123" },
  {
    useAdditionalQueryKey: ["instance"], // Final key: ['default', 'instance']
  },
);

File Uploads & RPC Options

Pass files and RPC options directly in the mutation variables:

// Single file upload
const mutation = useRpcMutation(rpc.files.uploadFile, {
  onSuccess: () => console.log("Uploaded!"),
});

mutation.mutate({
  body: { description: "My file" },
  file: fileInput.files[0],
});

// Multiple files
mutation.mutate({
  body: { category: "documents" },
  files: Array.from(fileInput.files),
});

// With custom RPC options (headers, axios instance, etc)
mutation.mutate({
  body: { description: "My file" },
  file: fileInput.files[0],
  rpcOptions: {
    requestOptions: {
      headers: { "X-Custom-Header": "value" },
    },
  },
});

// Static RPC options (applied to all mutations from this hook)
const useUploadFile = createRpcMutation(rpc.files.uploadFile, {
  invalidate: [rpc.files.listFiles],
  rpcOptions: {
    requestOptions: { timeout: 30000 },
  },
});

// Dynamic options override static ones
const uploadFile = useUploadFile();
uploadFile.mutate({
  body: { description: "My file" },
  file: fileInput.files[0],
  rpcOptions: {
    requestOptions: { timeout: 60000 }, // Overrides the 30000 from hook
  },
});

Automatic Cache Invalidation

Mutations can automatically invalidate related queries:

const useCreateUser = createRpcMutation(rpc.user.createUser, {
  invalidate: [
    rpc.user.listUsers, // Invalidate list
    rpc.user.getUserById, // Invalidate detail queries
  ],
});

// After successful mutation, all queries for these routes are invalidated

Custom Query Client

Pass a custom QueryClient instance for isolated query management:

const customQueryClient = new QueryClient();

const { data } = useRpcQuery(
  rpc.user.listUsers,
  undefined,
  {},
  customQueryClient, // Use specific client
);

💡 Complete Example

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { RpcClient } from '@nestjs-rpc/client';
import {
  createRpcQuery,
  createRpcMutation,
  useInvalidateRpcQuery,
} from '@nestjs-rpc/query';
import type { Manifest } from './nest-rpc.config';

// Setup
const rpcClient = new RpcClient<Manifest>({
  baseUrl: 'http://localhost:3000',
});
const rpc = rpcClient.routers();
const queryClient = new QueryClient();

// Create reusable hooks
const useUserList = createRpcQuery(rpc.user.listUsers, {
  staleTime: 60000,
});

const useCreateUser = createRpcMutation(rpc.user.createUser, {
  invalidate: [rpc.user.listUsers],
});

// Component
function UserManagement() {
  const { data: users, isLoading } = useUserList(undefined);
  const createUser = useCreateUser({
    onSuccess: () => {
      toast.success('User created!');
    },
  });
  const invalidate = useInvalidateRpcQuery();

  const handleCreate = () => {
    createUser.mutate({
      body: {
        name: 'John Doe',
        email: '[email protected]',
      },
    });
  };

  const handleRefresh = () => {
    invalidate(rpc.user.listUsers);
  };

  return (
    <div>
      <button onClick={handleCreate} disabled={createUser.isPending}>
        Create User
      </button>
      <button onClick={handleRefresh}>Refresh</button>

      {isLoading ? (
        <div>Loading...</div>
      ) : (
        <ul>
          {users.map((user) => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

// App
function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <UserManagement />
    </QueryClientProvider>
  );
}

🔒 Type Safety

The hooks automatically infer types from your RPC manifest:

// Server method signature:
@Route()
async getUserById(id: string): Promise<User> {
  return { id, name: 'John', email: '[email protected]' };
}

// Client automatically gets:
const { data } = useRpcQuery(rpc.user.getUserById, { id: '123' });
//    ^? { data: User }
//    ^? body parameter is typed as { id: string }
//    ^? Full autocomplete for User properties

If your server types change, your client code will show TypeScript errors immediately!


🎯 Best Practices

  1. Use factory pattern - Create reusable hooks with createRpcQuery and createRpcMutation
  2. Auto-invalidate - Use invalidate option in mutations to keep cache fresh
  3. Centralize RPC client - Create one RPC client instance and export it
  4. Handle errors - Use onError callbacks or error boundaries
  5. Use additional keys - Extend query keys when you need context-specific caching

📖 API Reference

Query Options

All standard React Query options are supported, except:

  • queryKey - Automatically generated
  • queryFn - Automatically generated
  • meta - Automatically generated

Mutation Options

All standard React Query mutation options are supported, plus:

  • invalidate?: { [PathSymbol]: string[] }[] - Routes to invalidate after success
  • rpcOptions?: Omit<RpcMethodOptions, "file" | "files"> - Static RPC options applied to all mutations (can be overridden per call)

Mutation Variables

Mutations accept RpcMutationBody<TBody> which includes:

  • body: TBody - The request body (required if TBody is required, optional otherwise)
  • file?: File - Single file to upload
  • files?: File[] - Multiple files to upload
  • rpcOptions?: Omit<RpcMethodOptions, "file" | "files"> - Per-call RPC options (overrides static options)

Query Key Structure

Query keys are automatically generated as:

[...path, body, rpcOptions, ...additionalKeys];

This ensures proper cache differentiation based on all parameters.


💡 Examples

Check out the example directory for a complete working example with React.


📚 Need More Help?

📖 Full Documentation →

Complete guides, API reference, advanced patterns, and troubleshooting


🔗 Related


🤝 Contributing

Contributions welcome! See the main README for details.

📄 License

MIT


Made with ❤️ for the NestJS community

⭐ Star us on GitHub📖 Documentation💬 Discussions