X Enterprises
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())

KeyTypeDescription
dataRef<T[]>Reactive list of fetched records.
totalRef<number>Server-reported total record count.
loadingRef<boolean>true while any async operation is in flight.
errorRef<Error | null>Last caught error, or null when clear.
fieldErrorsRef<Record<string, string[]>>422 field-level validation errors from failed create/update.
filters / search / sortRef<...>Reactive query state; changing them refetches.
page / perPage / setPage(n)paginationPresent when pagination is enabled.
refresh() => Promise<void>Re-fetch the current list.
create / update / remove / savefunctionsMutations; 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.
Copyright © 2026