{{ post.title }}
{{ post.content }}
By {{ post.author }}
Query with tanstack using generics
npm install gen-query[![npm version][npm-version-src]][npm-version-href]
[![npm downloads][npm-downloads-src]][npm-downloads-href]
[![License][license-src]][license-href]
[![Nuxt][nuxt-src]][nuxt-href]
A powerful Nuxt module for type-safe API queries using TanStack Query (Vue Query) with full TypeScript support and automatic cache management.
- โจ Release Notes
- ๐ Backend API Specification
- ๐ฅ Type-safe - Full TypeScript support with generics
- ๐ TanStack Query - Built on TanStack Query for powerful data fetching and caching
- ๐ฏ Composables-first - Easy-to-use Vue composables for all operations
- ๐ Pagination - Built-in infinite scroll and pagination support
- ๐ Smart Caching - Automatic cache invalidation with configurable update strategies
- ๐ Authentication - Optional token-based authentication
- ๐จ Flexible Filtering - Advanced filtering with multiple match modes
- ๐ฆ Lightweight - Minimal dependencies
Install the module:
``bash`
npx nuxi module add gen-query
Configure in nuxt.config.ts:
`typescript
export default defineNuxtConfig({
modules: ['gen-query'],
genQuery: {
baseURL: 'https://api.example.com', // API base URL
cachedPages: 4, // Max cached pages for infinite queries
update: UpdateStrategy.Invalidate, // Cache update strategy
},
})
`
| Option | Type | Default | Description |
| ------------- | ---------------- | ------------ | ------------------------------------------------------------------------------------------ |
| baseURL | string | '' | Base URL for all API requests. Can also be set via NUXT_PUBLIC_API_BASE_URL env variable |cachedPages
| | number | 4 | Maximum number of pages to cache in infinite queries |update
| | UpdateStrategy | Invalidate | Strategy for updating cache after mutations |
The module supports three cache update strategies:
- UpdateStrategy.None - No automatic cache updates. You manage cache manually.
- UpdateStrategy.Invalidate - Invalidates and refetches affected queries after mutations (recommended).
- UpdateStrategy.Optimistic - Immediately updates cache optimistically before server confirms.
`typescript
import { UpdateStrategy } from 'gen-query'
export default defineNuxtConfig({
genQuery: {
update: UpdateStrategy.Optimistic, // Use optimistic updates
},
})
`
`vue
{{ product.name }} - ${{ product.price }}
`
gen-query provides four main composables:
1. useLoginService - Handle user authentication
2. useSingleQuery - CRUD operations for a single entity by ID
3. useMultipleQuery - CRUD operations for collections
4. usePaginatedQuery - Paginated data with filtering and sorting
All composables return TanStack Query objects with reactive properties for data, loading states, errors, and mutation functions.
- Queries (read, list, page) - Fetch data with automatic caching and background refetchingcreate
- Mutations (, update, del) - Modify data with automatic cache updates based on your strategy
Use useLoginService for user authentication:
`vue
`
Use useSingleQuery to work with a single entity by ID:
`vue
{{ productQuery.read.data.value.description }} Price: ${{ productQuery.read.data.value.price }}
Error: {{ productQuery.read.error.value?.message }}
{{ productQuery.read.data.value.name }}
`
Use useMultipleQuery for CRUD operations on collections:
`vue
Price: ${{ product.price }} Category: {{ product.category }}
Error: {{ productQuery.read.error.value?.message }}
{{ product.name }}
`
Use usePaginatedQuery for paginated data with advanced filtering and sorting:
`vue
Price: ${{ product.price }} Category: {{ product.category }} Stock: {{ product.stock }} Added: {{ new Date(product.createdAt).toLocaleDateString() }} @click="productQuery.read.fetchNextPage()"
type="text"
placeholder="Search by name..."
@input="searchByName($event.target.value)"
/>
Price:
type="number"
placeholder="Min"
@change="updatePriceFilter($event.target.value, 500)"
/>
-
type="number"
placeholder="Max"
@change="updatePriceFilter(50, $event.target.value)"
/>
Error: {{ productQuery.read.error.value?.message }}
Showing {{ products.length }} of {{ totalElements }} products (Page {{ currentPage + 1 }} of
{{ totalPages }})
{{ product.name }}
@click="productQuery.read.fetchPreviousPage()"
:disabled="
!productQuery.read.hasPreviousPage.value ||
productQuery.read.isFetchingPreviousPage.value
"
>
{{ productQuery.read.isFetchingPreviousPage.value ? 'Loading...' : 'Previous Page' }}
:disabled="
!productQuery.read.hasNextPage.value || productQuery.read.isFetchingNextPage.value
"
>
{{ productQuery.read.isFetchingNextPage.value ? 'Loading...' : 'Next Page' }}
`
For infinite scroll, use all loaded pages:
`vue
{{ post.content }}
{{ post.title }}
By {{ post.author }}
Loading more posts...
`
#### useLoginService(resource?: string)
Creates a login service for authentication.
Parameters:
- resource (optional): API endpoint for login. Default: 'auth'
Returns:
`typescript`
{
login: {
mutate: (credentials: Login, options?) => void
isPending: Ref
isError: Ref
isSuccess: Ref
error: Ref
data: Ref
}
}
---
#### useSingleQuery
Fetches and manages a single entity by ID.
Type Parameters:
- T: Entity type extending EntityK
- : ID type (e.g., number, string)
Parameters:
- resource: API endpoint (e.g., 'products')id
- : Reactive reference to entity IDtoken
- (optional): Authentication token (reactive or getter)
Returns:
`typescript`
{
read: {
data: Ref
error: Ref
isLoading: Ref
isError: Ref
isSuccess: Ref
isFetching: Ref
status: Ref<'loading' | 'error' | 'success'>
refetch: () => Promise
},
create: {
mutate: (entity: T, options?) => void
isPending: Ref
isError: Ref
isSuccess: Ref
error: Ref
},
update: { / same as create / },
del: { / same as create / }
}
---
#### useMultipleQuery
Provides CRUD operations for a collection of entities.
Type Parameters:
- T: Entity type extending EntityK
- : ID type
Parameters:
- resource: API endpointtoken
- (optional): Authentication token
Returns:
`typescript`
{
read: {
data: Ref
error: Ref
isLoading: Ref
isError: Ref
isSuccess: Ref
isFetching: Ref
refetch: () => Promise
},
create: { / mutation object / },
update: { / mutation object / },
del: { / mutation object / }
}
---
#### usePaginatedQuery
Fetches paginated entities with filtering and sorting.
Type Parameters:
- T: Entity type extending EntityK
- : ID type
Parameters:
- resource: API endpointpageable
- : Pagination configurationfilters
- : Reactive filterstoken
- (optional): Authentication token
Returns:
`typescript`
{
read: {
data: Ref
error: Ref
isLoading: Ref
isError: Ref
isSuccess: Ref
isFetching: Ref
isFetchingNextPage: Ref
isFetchingPreviousPage: Ref
hasNextPage: Ref
hasPreviousPage: Ref
fetchNextPage: () => Promise
fetchPreviousPage: () => Promise
refetch: () => Promise
},
create: { / mutation object / },
update: { / mutation object / },
del: { / mutation object / }
}
---
#### Entity
Base interface for entities with an ID.
`typescript`
interface Entity
id: K
}
Example:
`typescript`
interface Product extends Entity
name: string
price: number
}
---
#### Login
Login credentials.
`typescript`
type Login = {
username: string
password: string
}
---
#### User
User information returned after login.
`typescript`
type User = {
username: string
password: string
fullName: string
email: string
token: string
}
---
#### Page
Paginated response structure.
`typescript`
type Page
page: {
number: number // Current page (0-indexed)
size: number // Items per page
totalElements: number // Total items across all pages
totalPages: number // Total number of pages
}
content: T[] // Items in current page
}
---
#### Sort
Sorting configuration.
`typescript`
type Sort = {
property: string // Field to sort by
direction: 'asc' | 'desc' // Sort direction
}
---
#### FilterItem
Filter configuration.
`typescript`
type FilterItem = {
operator: string // 'and' or 'or'
constraints: Constraint[]
}
---
#### Constraint
Filter constraint.
`typescript`
type Constraint = {
matchMode: string // eq, ne, lt, lte, gt, gte, contains, startsWith, endsWith, in
value: unknown // Filter value
}
---
#### Pageable
Pagination configuration class.
`typescript
class Pageable {
constructor(page: number = 0, size: number = 30, sort: Sort[] = [])
toQueryParams(): string
}
`
Example:
`typescript`
const pageable = new Pageable(
0, // first page
20, // 20 items per page
[
{ property: 'name', direction: 'asc' },
{ property: 'price', direction: 'desc' },
]
)
---
#### Filters
Filter configuration class.
`typescript
class Filters {
[key: string]: FilterItem
toQueryParams(): string
}
`
Example:
`typescript
const filters = new Filters()
// Single constraint
filters.category = {
operator: 'and',
constraints: [{ matchMode: 'eq', value: 'electronics' }],
}
// Multiple constraints (AND)
filters.price = {
operator: 'and',
constraints: [
{ matchMode: 'gte', value: 100 },
{ matchMode: 'lte', value: 500 },
],
}
// Multiple constraints (OR)
filters.status = {
operator: 'or',
constraints: [
{ matchMode: 'eq', value: 'active' },
{ matchMode: 'eq', value: 'pending' },
],
}
// String matching
filters.name = {
operator: 'and',
constraints: [{ matchMode: 'contains', value: 'laptop' }],
}
// Date filtering
filters.createdAt = {
operator: 'and',
constraints: [{ matchMode: 'gte', value: new Date('2024-01-01') }],
}
`
Available Match Modes:
| Match Mode | Description | Example |
| ------------ | --------------------- | ---------------------------------------------- |
| eq | Equals | { matchMode: 'eq', value: 'active' } |ne
| | Not equals | { matchMode: 'ne', value: 'deleted' } |lt
| | Less than | { matchMode: 'lt', value: 100 } |lte
| | Less than or equal | { matchMode: 'lte', value: 100 } |gt
| | Greater than | { matchMode: 'gt', value: 50 } |gte
| | Greater than or equal | { matchMode: 'gte', value: 50 } |contains
| | Contains substring | { matchMode: 'contains', value: 'laptop' } |startsWith
| | Starts with | { matchMode: 'startsWith', value: 'Pro' } |endsWith
| | Ends with | { matchMode: 'endsWith', value: 'Pro' } |in
| | In list | { matchMode: 'in', value: 'active,pending' } |
---
#### ApiError
Custom error class for API errors.
`typescript`
class ApiError extends Error {
timestamp: Date
type: string
statusCode: number
status: string
content?: object
stack?: string
}
Example:
`typescript
const { error } = productQuery.read
if (error.value) {
console.error('Error:', error.value.message)
console.error('Status:', error.value.statusCode)
console.error('Details:', error.value.content)
}
`
---
#### UpdateStrategy
Cache update strategy for mutations.
`typescript`
enum UpdateStrategy {
None, // No automatic cache updates
Invalidate, // Invalidate and refetch (recommended)
Optimistic, // Optimistic updates
}
Usage:
`typescript
import { UpdateStrategy } from 'gen-query'
export default defineNuxtConfig({
genQuery: {
update: UpdateStrategy.Optimistic,
},
})
`
Your backend API must follow the REST specification detailed in BACKEND_API.md.
Key requirements:
- RESTful endpoints with JSON request/response
- Standard CRUD operations (GET, POST, PUT, DELETE)
- Pagination endpoint with /page suffix
- Support for filter and sort query parameters
- Consistent error response format
- ISO 8601 date format
- Optional Bearer token authentication
`bashInstall dependencies
npm install
MIT License - see LICENSE for details
- NPM Package
- GitHub Repository
- TanStack Query Documentation
[npm-version-src]: https://img.shields.io/npm/v/gen-query/latest.svg?style=flat&colorA=020420&colorB=00DC82
[npm-version-href]: https://npmjs.com/package/gen-query
[npm-downloads-src]: https://img.shields.io/npm/dm/gen-query.svg?style=flat&colorA=020420&colorB=00DC82
[npm-downloads-href]: https://npm.chart.dev/gen-query
[license-src]: https://img.shields.io/npm/l/gen-query.svg?style=flat&colorA=020420&colorB=00DC82
[license-href]: https://npmjs.com/package/gen-query
[nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt.js
[nuxt-href]: https://nuxt.com