@phamphong94/query-client
v1.0.3
Published
A lightweight TypeScript Query Client for React
Downloads
308
Readme
Query Client
A 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.
Features
- 🚀 Zero Boilerplate: No more manually writing
useQuery,queryKey, orqueryFnfor 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,fetchwrappers, or env configs. - 🔄 Smart Invalidation: Declare invalidation logic right next to your mutation definition.
Installation
npm install query-client @tanstack/react-query
# or
yarn add query-client @tanstack/react-queryQuick Start
1. Define your API Class
Write a standard TypeScript class for your API calls.
// 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<User[]> {
return fetch(`${this.baseUrl}/users?page=${params.page}`).then((r) =>
r.json()
);
}
async create(name: string): Promise<User> {
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"]],
},
},
});2. Create the Client
Initialize the client with a registry of your API definitions.
// src/api/client.ts
import { createClient } from "query-client";
import { userApiDef } from "./user.api";
export const client = createClient({
user: userApiDef,
// post: postApiDef,
// ...other APIs
});3. Use in Components
Enjoy fully typed hooks!
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 <div>Loading...</div>;
return (
<div>
<ul>
{data?.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
<button
onClick={() => {
// ✅ Typed variables: string
createMutation.mutate("New User");
}}
>
Add User
</button>
</div>
);
}Advanced Usage
Custom Query Keys
By default, keys are [scope, method, params]. You can override this:
queries: {
list: {
getQueryKey: (params) => ["custom-key", params.id]
}
}Invalidation Shortcuts
Trigger invalidations automatically after mutation success. Partial matching works standardly (e.g., ["users"] invalidates all user queries).
mutations: {
update: {
invalidates: [
["users", "list"],
["users", "detail"]
]
}
}Running Examples
This repository includes fully working examples.
1. Basic Example (User API)
Demonstrates usage with fetch, dependency injection, and simple list/create operations.
npm run example:basic2. Post API
A simpler example showing minimal configuration.
npm run example:postLicense
ISC
