Documentation Index
Fetch the complete documentation index at: https://docs-mstore.faisalaffan.com/llms.txt
Use this file to discover all available pages before exploring further.
API Layer Pattern
Setiap feature module memiliki API layer yang terorganisir untuk menangani komunikasi HTTP dengan backend.API Layer Structure
features/{domain}/api/
├── index.ts # Barrel export
├── getList.ts # GET collection
├── getById.ts # GET single item
├── create.ts # POST create
├── update.ts # PUT/PATCH update
├── delete.ts # DELETE
└── {custom}.ts # Custom endpoints
Standard CRUD Operations
Get List (Collection)
// features/03_inventory/api/getList.ts
import type { Inventory, InventoryQueryParams, PaginatedResponse } from '../types'
export const getInventoryList = async (
params?: InventoryQueryParams
): Promise<PaginatedResponse<Inventory>> => {
const { $api } = useNuxtApp()
// Build query params
const query: Record<string, string | number | undefined> = {}
if (params?.page) query.page = params.page
if (params?.limit) query.limit = params.limit
if (params?.search) query.search = params.search
if (params?.sortBy) query.sortBy = params.sortBy
if (params?.sortOrder) query.sortOrder = params.sortOrder
if (params?.category) query.category = params.category
if (params?.status) query.status = params.status
return await $api<PaginatedResponse<Inventory>>('/api/v1/inventory', {
method: 'GET',
query,
})
}
Get By ID (Single Item)
// features/03_inventory/api/getById.ts
import type { Inventory } from '../types'
export const getInventoryById = async (id: string): Promise<Inventory> => {
const { $api } = useNuxtApp()
return await $api<Inventory>(`/api/v1/inventory/${id}`, {
method: 'GET',
})
}
Create (POST)
// features/03_inventory/api/create.ts
import type { Inventory, CreateInventoryDto } from '../types'
export const createInventory = async (
data: CreateInventoryDto
): Promise<Inventory> => {
const { $api } = useNuxtApp()
return await $api<Inventory>('/api/v1/inventory', {
method: 'POST',
body: data,
})
}
Update (PUT/PATCH)
// features/03_inventory/api/update.ts
import type { Inventory, UpdateInventoryDto } from '../types'
export const updateInventory = async (
id: string,
data: UpdateInventoryDto
): Promise<Inventory> => {
const { $api } = useNuxtApp()
return await $api<Inventory>(`/api/v1/inventory/${id}`, {
method: 'PUT',
body: data,
})
}
// Partial update
export const patchInventory = async (
id: string,
data: Partial<UpdateInventoryDto>
): Promise<Inventory> => {
const { $api } = useNuxtApp()
return await $api<Inventory>(`/api/v1/inventory/${id}`, {
method: 'PATCH',
body: data,
})
}
Delete
// features/03_inventory/api/delete.ts
export const deleteInventory = async (id: string): Promise<void> => {
const { $api } = useNuxtApp()
await $api(`/api/v1/inventory/${id}`, {
method: 'DELETE',
})
}
// Bulk delete
export const bulkDeleteInventory = async (ids: string[]): Promise<void> => {
const { $api } = useNuxtApp()
await $api('/api/v1/inventory/bulk-delete', {
method: 'POST',
body: { ids },
})
}
Barrel Export
// features/03_inventory/api/index.ts
export { getInventoryList } from './getList'
export { getInventoryById } from './getById'
export { createInventory } from './create'
export { updateInventory, patchInventory } from './update'
export { deleteInventory, bulkDeleteInventory } from './delete'
// Custom exports
export { importInventory } from './import'
export { exportInventory } from './export'
Type Definitions
// features/03_inventory/types/inventory.types.ts
// Entity
export interface Inventory {
id: string
name: string
sku: string
price: number
stock: number
minStock: number
category: string
status: 'active' | 'inactive'
createdAt: string
updatedAt: string
}
// Query params
export interface InventoryQueryParams {
page?: number
limit?: number
search?: string
sortBy?: keyof Inventory
sortOrder?: 'asc' | 'desc'
category?: string
status?: 'active' | 'inactive'
}
// Create DTO (Data Transfer Object)
export interface CreateInventoryDto {
name: string
sku: string
price: number
stock: number
minStock?: number
category: string
}
// Update DTO
export interface UpdateInventoryDto {
name?: string
sku?: string
price?: number
stock?: number
minStock?: number
category?: string
status?: 'active' | 'inactive'
}
// Paginated response
export interface PaginatedResponse<T> {
data: T[]
meta: {
total: number
page: number
limit: number
totalPages: number
}
}
Custom API Functions
Import/Export
// features/03_inventory/api/import.ts
export interface ImportResult {
success: number
failed: number
errors: Array<{
row: number
message: string
}>
}
export const importInventory = async (file: File): Promise<ImportResult> => {
const { $api } = useNuxtApp()
const formData = new FormData()
formData.append('file', file)
return await $api<ImportResult>('/api/v1/inventory/import', {
method: 'POST',
body: formData,
})
}
export const validateImport = async (file: File): Promise<{
valid: boolean
errors: string[]
preview: Inventory[]
}> => {
const { $api } = useNuxtApp()
const formData = new FormData()
formData.append('file', file)
return await $api('/api/v1/inventory/import/validate', {
method: 'POST',
body: formData,
})
}
// features/03_inventory/api/export.ts
export const exportInventory = async (
format: 'xlsx' | 'csv',
params?: InventoryQueryParams
): Promise<Blob> => {
const { $api } = useNuxtApp()
return await $api<Blob>('/api/v1/inventory/export', {
method: 'GET',
query: { format, ...params },
responseType: 'blob',
})
}
Stock Operations
// features/03_inventory/api/stock.ts
export interface StockAdjustment {
productId: string
quantity: number
type: 'in' | 'out' | 'adjustment'
reason: string
reference?: string
}
export const adjustStock = async (
data: StockAdjustment
): Promise<Inventory> => {
const { $api } = useNuxtApp()
return await $api<Inventory>('/api/v1/inventory/stock/adjust', {
method: 'POST',
body: data,
})
}
export const transferStock = async (data: {
productId: string
quantity: number
fromWarehouse: string
toWarehouse: string
}): Promise<void> => {
const { $api } = useNuxtApp()
await $api('/api/v1/inventory/stock/transfer', {
method: 'POST',
body: data,
})
}
Error Handling in API Layer
// features/03_inventory/api/getList.ts
import type { Inventory, InventoryQueryParams, PaginatedResponse } from '../types'
export const getInventoryList = async (
params?: InventoryQueryParams
): Promise<PaginatedResponse<Inventory>> => {
const { $api } = useNuxtApp()
try {
return await $api<PaginatedResponse<Inventory>>('/api/v1/inventory', {
method: 'GET',
query: params,
})
} catch (error) {
// Transform specific errors
if (error instanceof FetchError) {
if (error.statusCode === 403) {
throw new Error('You do not have permission to view inventory')
}
if (error.statusCode === 404) {
throw new Error('Inventory not found')
}
}
// Re-throw for store/composable to handle
throw error
}
}
API with Caching
// features/03_inventory/api/getList.ts
export const getInventoryList = async (
params?: InventoryQueryParams,
options?: { cache?: boolean }
): Promise<PaginatedResponse<Inventory>> => {
const { $api } = useNuxtApp()
// Use Nuxt's built-in caching for GET requests
if (options?.cache) {
const cacheKey = `inventory-list-${JSON.stringify(params)}`
return await useAsyncData(cacheKey, () =>
$api<PaginatedResponse<Inventory>>('/api/v1/inventory', {
query: params,
})
).then(({ data }) => data.value!)
}
return await $api<PaginatedResponse<Inventory>>('/api/v1/inventory', {
query: params,
})
}
Using API in Store
// features/03_inventory/store/inventory.store.ts
import { defineStore } from 'pinia'
import {
getInventoryList,
getInventoryById,
createInventory,
updateInventory,
deleteInventory,
} from '../api'
import type { Inventory, InventoryQueryParams, PaginatedResponse } from '../types'
export const useInventoryStore = defineStore('inventory', () => {
const items = ref<Inventory[]>([])
const currentItem = ref<Inventory | null>(null)
const loading = ref(false)
const error = ref<string | null>(null)
const meta = ref({
total: 0,
page: 1,
limit: 20,
totalPages: 0,
})
// Fetch list
const fetchItems = async (params?: InventoryQueryParams) => {
loading.value = true
error.value = null
try {
const response = await getInventoryList(params)
items.value = response.data
meta.value = response.meta
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to fetch'
throw e
} finally {
loading.value = false
}
}
// Fetch single
const fetchItem = async (id: string) => {
loading.value = true
error.value = null
try {
currentItem.value = await getInventoryById(id)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to fetch'
throw e
} finally {
loading.value = false
}
}
// Create
const addItem = async (data: CreateInventoryDto) => {
loading.value = true
try {
const newItem = await createInventory(data)
items.value.push(newItem)
return newItem
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to create'
throw e
} finally {
loading.value = false
}
}
// Update
const editItem = async (id: string, data: UpdateInventoryDto) => {
try {
const updated = await updateInventory(id, data)
const index = items.value.findIndex(item => item.id === id)
if (index !== -1) {
items.value[index] = updated
}
return updated
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to update'
throw e
}
}
// Delete
const removeItem = async (id: string) => {
try {
await deleteInventory(id)
items.value = items.value.filter(item => item.id !== id)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to delete'
throw e
}
}
return {
items,
currentItem,
loading,
error,
meta,
fetchItems,
fetchItem,
addItem,
editItem,
removeItem,
}
})
Best Practices
One File Per Endpoint
One File Per Endpoint
api/
├── getList.ts # GET /inventory
├── getById.ts # GET /inventory/:id
├── create.ts # POST /inventory
├── update.ts # PUT /inventory/:id
└── delete.ts # DELETE /inventory/:id
Type Everything
Type Everything
- Define types for request params
- Define types for request body (DTOs)
- Define types for response
- Export types from feature module
Use Barrel Exports
Use Barrel Exports
// features/03_inventory/index.ts
export * from './api'
export * from './types'
export * from './store'
// Usage
import { getInventoryList, Inventory } from '~/features/03_inventory'
Handle Errors Appropriately
Handle Errors Appropriately
- API layer: Transform HTTP errors to meaningful messages
- Store: Store error state for UI
- Composable: Show toast/notification
- Component: Display error UI
Next Steps
Data Flow
Complete data flow patterns
State Management
Pinia store patterns