Skip to content

Models Package

The packages/models directory contains shared TypeScript interfaces for API responses, particularly for Laravel-style paginated responses.


The main response wrapper for paginated API responses.

export interface LaravelResponse<T> {
data?: Array<T>;
meta?: Meta;
}
import type { LaravelResponse } from '@ion-components/packages/models/api.model';
interface Product {
id: number;
name: string;
price: number;
}
// API response type
type ProductsResponse = LaravelResponse<Product>;
// Usage with Axios
async function fetchProducts(): Promise<Product[]> {
const response = await axios.get<ProductsResponse>('/api/products');
return response.data.data ?? [];
}

Pagination and filtering metadata.

export interface Meta {
filter: string;
search: string | null;
sort_by: string;
sort_order: string;
count: number;
total_pages: number;
current_page: number;
from: number;
last_page: number;
links: Link[];
path: string;
per_page: number;
to: number;
total: number;
}
PropertyTypeDescription
filterstringCurrent filter applied
searchstring | nullSearch query if any
sort_bystringField being sorted by
sort_orderstringSort direction (asc or desc)
countnumberItems in current page
total_pagesnumberTotal number of pages
current_pagenumberCurrent page number
fromnumberFirst item index on page
tonumberLast item index on page
last_pagenumberLast page number
per_pagenumberItems per page
totalnumberTotal items across all pages
pathstringBase API path
linksLink[]Pagination links

Pagination link structure.

export interface Link {
url?: null | string;
label?: string;
active?: boolean;
}
PropertyTypeDescription
urlstring | nullURL for this page (null if not available)
labelstringDisplay label (number or “Previous”/“Next”)
activebooleanWhether this is the current page

<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import type { LaravelResponse, Meta } from '@ion-components/packages/models/api.model';
import axios from 'axios';
interface User {
id: number;
name: string;
email: string;
role: string;
}
const users = ref<User[]>([]);
const meta = ref<Meta | null>(null);
const currentPage = ref(1);
const perPage = ref(10);
const loading = ref(false);
const totalPages = computed(() => meta.value?.total_pages ?? 0);
const showing = computed(() => {
if (!meta.value) return '';
return `Showing ${meta.value.from} to ${meta.value.to} of ${meta.value.total}`;
});
async function fetchUsers(page = 1) {
loading.value = true;
try {
const response = await axios.get<LaravelResponse<User>>('/api/users', {
params: {
page,
per_page: perPage.value,
},
});
users.value = response.data.data ?? [];
meta.value = response.data.meta ?? null;
currentPage.value = meta.value?.current_page ?? 1;
} finally {
loading.value = false;
}
}
function goToPage(page: number) {
if (page >= 1 && page <= totalPages.value) {
fetchUsers(page);
}
}
onMounted(() => fetchUsers());
</script>
<template>
<div>
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<tr v-for="user in users" :key="user.id">
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
<td>{{ user.role }}</td>
</tr>
</tbody>
</table>
<!-- Pagination -->
<div class="pagination">
<span>{{ showing }}</span>
<div class="pagination-controls">
<button
@click="goToPage(currentPage - 1)"
:disabled="currentPage <= 1"
>
Previous
</button>
<template v-for="link in meta?.links" :key="link.label">
<button
v-if="link.url !== null"
@click="goToPage(Number(link.label))"
:class="{ active: link.active }"
>
{{ link.label }}
</button>
</template>
<button
@click="goToPage(currentPage + 1)"
:disabled="currentPage >= totalPages"
>
Next
</button>
</div>
</div>
</div>
</template>

packages/models/api.model.ts
export interface LaravelResponse<T> {
data?: Array<T>;
meta?: Meta;
}
export interface Meta {
filter: string;
search: string | null;
sort_by: string;
sort_order: string;
count: number;
total_pages: number;
current_page: number;
from: number;
last_page: number;
links: Link[];
path: string;
per_page: number;
to: number;
total: number;
}
export interface Link {
url?: null | string;
label?: string;
active?: boolean;
}