Composables
useXCrud
Chainable CRUD composable with stale-while-revalidate caching, optimistic updates, request deduplication, auto-retry, and optional server-side pagination.
useXCrud
Zero-config CRUD for a REST endpoint. Pass a resource path and chain a mode — .all() for lists, .read(id) for a single record, .create() for a new-record form. Includes a stale-while-revalidate cache, optimistic updates, request deduplication, auto-retry with backoff, and opt-in server-side pagination. Pairs directly with XATable.
Breaking (v0.6.0, 2026-07): the v1 flat API (
useXCrud('/api/users') returning items/fetch) was replaced by the chainable API below. Pass the resource without a leading slash ('users', not '/users').v3 (nuxt-x-app 0.6.0) — additive, NOT breaking. The v2 chainable API is untouched; v3 layers on opt-in capabilities:
pagination (server-side page/perPage/setPage(), sent as page/limit params, included in the cache key), scope (string or Ref cache-scope segment with static useXCrud.clearScope() — a Ref scope makes live lists refetch on scope change), fieldErrors (422 field-level validation errors from failed create/update), sortStyle: 'params' | 'dash', optimistic create() (temp-id insert → replace/rollback), and the auth pipeline (every request injects Authorization: Bearer from a plugin-provided $getAuthToken, exactly like useXFetch; per-call headers win). Nothing to migrate — existing v2 call sites work unchanged.Usage
// List mode
const { data, loading, filters, search, total, refresh, create, remove } = useXCrud<User>('users').all()
// Detail mode — `form` auto-syncs with the fetched record
const { data, form, formDirty, save, update, remove, resetForm } = useXCrud<User>('users').read(id)
// Create mode
const { form, save } = useXCrud<User>('users').create()
// Async/await usage
const { data } = await useXCrud('users').read(123)
Options
useXCrud('users', {
idKey: 'id',
showToast: true,
initialFilters: { status: 'active' },
initialSort: { column: 'name', direction: 'asc' },
transform: (data) => data.items,
headers: () => ({ Authorization: `Bearer ${token}` }),
onFetch: (data) => console.log(data),
onError: (err) => console.error(err),
onCreated: (item) => router.push(`/users/${item.id}`),
onUpdated: (item) => toast.success('Updated'),
onDeleted: () => router.push('/users'),
// Caching / resilience (defaults shown)
cache: true, // stale-while-revalidate via useState
staleTime: 30_000, // ms before a cached list refetches in background
optimistic: true, // optimistic create/update/remove
retry: 3, // fetch retries with exponential backoff
retryDelay: 1000, // base retry delay in ms
updateMethod: 'PUT', // 'PUT' | 'PATCH'
searchDebounce: 300, // search debounce in ms (0 = immediate)
errorMessages: {
fetch: 'Failed to load',
create: 'Failed to create',
update: 'Failed to update',
delete: 'Failed to delete',
},
// Server-side pagination (opt-in; page is 1-based, sent as page/limit)
pagination: true, // or { page: 1, perPage: 20 }
scope: 'acme', // cache scope (string or Ref) for multi-tenant lists
sortStyle: 'params', // 'params' (sort/order) | 'dash' (sort=-col)
})
Returns (.all())
| Key | Type | Description |
|---|---|---|
data | Ref<T[]> | Reactive list of fetched records. |
total | Ref<number> | Server-reported total record count. |
loading | Ref<boolean> | true while any async operation is in flight. |
error | Ref<Error | null> | Last caught error, or null when clear. |
fieldErrors | Ref<Record<string, string[]>> | 422 field-level validation errors from failed create/update. |
filters / search / sort | Ref<...> | Reactive query state; changing them refetches. |
page / perPage / setPage(n) | pagination | Present when pagination is enabled. |
refresh | () => Promise<void> | Re-fetch the current list. |
create / update / remove / save | functions | Mutations; auto-invalidate the endpoint cache. |
invalidateCache | () => Promise<void> | Manually clear this endpoint's cache and refetch. |
Cache invalidation
// Automatic: mutations auto-invalidate the endpoint cache
await create(payload)
// Manual, from the .all() return
const { invalidateCache } = useXCrud('users').all()
await invalidateCache()
// Static, from anywhere
useXCrud.clearCache('users') // one endpoint
useXCrud.clearCache() // all caches
useXCrud.clearScope('acme') // all caches under a scope
Example
<script setup lang="ts">
const { data, loading, refresh, remove } = useXCrud<User>('users').all()
const { presets } = useXTableColumns()
const { success } = useXToast()
const columns = [
presets.avatar('name', 'User'),
presets.email('email'),
presets.badge('status', 'Status'),
presets.date('createdAt', 'Joined'),
presets.actions(),
]
async function deleteUser(id: string | number) {
await remove(id)
success('User deleted')
}
</script>
<template>
<XATable :data="data" :columns="columns" :loading="loading" :on-refresh="refresh" @delete="deleteUser" />
</template>
AI Context
composable: useXCrud
package: "@xenterprises/nuxt-x-app"
use-when: >
Any page that needs to list, create, edit, or delete records from a REST endpoint.
