A lightweight TypeScript Query Client for React
npm install @phamphong94/query-clientA lightweight, strictly typed TypeScript wrapper for TanStack Query (React Query) that generates hooks from your API class definitions. Use your existing API classes to get fully typed useQuery and useMutation hooks without the boilerplate.
- 🚀 Zero Boilerplate: No more manually writing useQuery, queryKey, or queryFn for every endpoint.
- 💎 Total Type Safety: Arguments and return types are inferred directly from your API methods.
- 🔑 Automatic Query Keys: Consistent [scope, method, params] keys generated automatically, or fully customizable.
- 💉 Dependency Injection: Use standard Classes for your API, allowing easy injection of axios, fetch wrappers, or env configs.
- 🔄 Smart Invalidation: Declare invalidation logic right next to your mutation definition.
``bash`
npm install query-client @tanstack/react-queryor
yarn add query-client @tanstack/react-query
Write a standard TypeScript class for your API calls.
`typescript
// src/api/user.api.ts
import { defineApi } from "query-client";
interface User {
id: string;
name: string;
}
export class UserApi {
constructor(private baseUrl: string) {}
async list(params: { page: number }): Promise
return fetch(${this.baseUrl}/users?page=${params.page}).then((r) =>
r.json()
);
}
async create(name: string): Promise
return fetch(${this.baseUrl}/users, {
method: "POST",
body: JSON.stringify({ name }),
}).then((r) => r.json());
}
}
// Wrap it with defineApi to configure Query/Mutation behavior
export const userApiDef = defineApi(new UserApi("/api"), {
queries: {
list: {
// Optional: Custom query key
getQueryKey: (params) => ["users", "list", params.page],
},
},
mutations: {
create: {
// Optional: Auto-invalidate list on success
invalidates: [["users", "list"]],
},
},
});
`
Initialize the client with a registry of your API definitions.
`typescript
// src/api/client.ts
import { createClient } from "query-client";
import { userApiDef } from "./user.api";
export const client = createClient({
user: userApiDef,
// post: postApiDef,
// ...other APIs
});
`
Enjoy fully typed hooks!
`tsx
import { client } from "./api/client";
function UserList() {
// ✅ Typed params: { page: number } is required
// ✅ Typed data: User[] | undefined
const { data, isLoading } = client.from("user").useList({ page: 1 });
const createMutation = client.from("user").useCreate();
if (isLoading) return
return (
Advanced Usage
$3
By default, keys are [scope, method, params]. You can override this:
`typescript
queries: {
list: {
getQueryKey: (params) => ["custom-key", params.id]
}
}
`$3
Trigger invalidations automatically after mutation success. Partial matching works standardly (e.g., ["users"] invalidates all user queries).
`typescript
mutations: {
update: {
invalidates: [
["users", "list"],
["users", "detail"]
]
}
}
`Running Examples
This repository includes fully working examples.
$3
Demonstrates usage with fetch, dependency injection, and simple list/create operations.
`bash
npm run example:basic
`$3
A simpler example showing minimal configuration.
`bash
npm run example:post
``ISC