This Nuxt Module provides an opinionated API for Laravel Model Indexes.
It is recommended to use the Laravel Model Index package in your Laravel project.
- Laravel API index queries with URL-encoded parameters
- Complex Filtering
- Pagination
- Searching
- Sorting
- Mutateable Index State
Add @aw-studio/nuxt-laravel-model-index
as a dependency in your project:
npm i @aw-studio/nuxt-laravel-model-index
Add the module to your nuxt.config.ts
file and configure the settings:
export default defineNuxtConfig({
modules: [
'@aw-studio/nuxt-laravel-model-index',
],
modelIndex: {
baseUrl: 'https://your-laravel-api.tld',
},
});
You can now create indexes for all your Laravel models.
The useModelIndex
composable is auto-imported. Pass the relative path to your API endpoint as a parameter, which also serves as the identifier for the state object. Ensure this parameter is unique.
Create a composable for reusability:
import { ModelIndexOptions } from '@aw-studio/nuxt-laravel-model-index';
import { Product } from '@/types';
export const useProducts = (options?: ModelIndexOptions) => {
return useModelIndex<Product>('/api/products', options);
};
This composable now provides a stateful, filterable Laravel API index.
You can set up the index configuration when initializing the composable:
const {items} = await useJobPostings({
perPage: 6,
syncUrl: true,
sort: 'title',
ssr: true,
})
You can also dynamically update the configuration during runtime:
const { setConfig, setPerPage, setSyncUrl } = await useProducts();
setConfig({
perPage: 6,
syncUrl: true,
});
setPerPage(10);
setSyncUrl(false);
Enable SSR compatibility by passing the ssr
option. Items will be loaded during server-side rendering:
const { items } = await useProducts({
perPage: 6,
syncUrl: true,
sort: 'title',
ssr: true,
});
These functions allow you to interact with the API:
const { load, loadAll, loadMore, nextPage, prevPage } = await useProducts();
// Load the first page
await load();
// Load the next page
await nextPage();
// Load the previous page
await prevPage();
// Load a specific page (e.g., page 6)
await load(6);
// Append more items (useful for infinite scrolling)
await loadMore();
The model items are stored in a reactive state:
const { items } = await useProducts()
Trigger a search by setting a search string:
const { setSearch } = await useProducts();
setSearch('Foo');
Apply filters by passing a filter object:
const { setFilter } = await useProducts()
// Basic filter
setFilter({
size: 'M',
color: 'blue'
})
// Filter with operators
setFilter({
price: { $lt: 100 },
});
// Complex conditions
setFilter({
$and: [
{
$or: [
{ title: { $contains: 'John' } },
{ title: { $contains: 'Paul' } },
],
},
{ title: { $contains: 'John' } },
{ price: { $lt: 100 } },
{ size: { $in: ['S', 'M'] } },
],
});
| Operator | Description |
---|
| $eq
| Equal to |
| $ne
| Not equal to |
| $lt
| Less than |
| $lte
| Less than or equal to |
| $gt
| Greater than |
| $gte
| Greater than or equal to|
| $in
| In array |
| $notIn
| Not in array |
| $contains
| Contains |
| $notContains
| Does not contain |
| $between
| Between values |
Sort the index with the following syntax:
const { setSort } = await useProducts();
// Sort ascending by title
setSort('title');
// Sort descending by title
setSort('title:desc');
setSort('-title');
Pagination metadata and loading state are available:
<template>
<div>
<Spinner v-if="loading">
Page: {{ meta?.current_page }} / {{ meta?.last_page }}
</div>
</template>
<script setup lang="ts">
const { meta, loading } = await useProducts()
</script>
import { ModelIndexOptions } from '@aw-studio/nuxt-laravel-model-index';
import { Product } from '@/types';
export const useProducts = (options?: ModelIndexOptions) => {
return useModelIndex<Product>('/api/products', options);
};
<template>
<div>
<YourFilterComponent />
<YourProductComponent
v-for="item in items"
:key="item.id"
:product="item"
/>
<button @click="prevPage" :disabled="!hasPrevPage">Previous</button>
<button @click="nextPage" :disabled="!hasNextPage">Next</button>
<div>Page: {{ meta?.current_page }} / {{ meta?.last_page }}</div>
</div>
</template>
<script setup lang="ts">
const { items, meta, hasNextPage, hasPrevPage, nextPage, prevPage, load, setConfig } = await useProducts();
onMounted(async () => {
setConfig({
perPage: 6,
syncUrl: true,
});
await load();
});
</script>
<template>
<div>
<input v-model="searchTerm" type="search" placeholder="Search" />
</div>
</template>
<script setup lang="ts">
const { setSearch } = await useProducts();
const searchTerm = ref('');
watch(searchTerm, () => {
setSearch(searchTerm.value);
});
</script>
<template>
<div>
<label
v-for="size in sizes"
:key="size"
>
<input
v-model="selectedSizes"
type="checkbox"
:value="size"
/>
{{ size }}
</label>
</div>
</template>
<script setup lang="ts">
const { setFilter } = await useProducts();
const sizes = ['S', 'M', 'L', 'XL']
const selectedSizes = ref<string[]>([])
const filter = computed(()=>{
if (selectedSizes.length > 0) {
filter.$or = selectedSizes.value.map((size: string) => {
return {
size
}
})
}
})
watch(
() => filter.value,
() => {
setFilter(filter.value)
},
{ deep: true }
)
</script>