Стандарты
API запросы ($fetch.create)
Используем $fetch.create() для создания преконфигурированного HTTP-клиента.
Используем $fetch.create() для создания преконфигурированного HTTP-клиента.
Документация Nuxt:
- https://nuxt.com/docs/4.x/guide/recipes/custom-usefetch
- https://nuxt.com/docs/4.x/examples/advanced/use-custom-fetch-composable
Подход
- Публичные запросы —
$fetchнапрямую - Авторизованные запросы —
useAuthFetch()изauth-layer(построен на$fetch.create)
Публичные запросы
// Без авторизации — $fetch напрямую
const services = await $fetch<Service[]>('/api/public/services')
Авторизованные запросы
// С авторизацией — useAuthFetch из auth-layer
const authFetch = useAuthFetch()
// GET
const users = await authFetch<User[]>('/users')
// GET с query params
const orders = await authFetch<Order[]>('/orders', {
query: { page: 1, status: 'active' }
})
// POST
const newUser = await authFetch<User>('/users', {
method: 'POST',
body: { name: 'John', email: 'john@example.com' }
})
// PATCH
await authFetch(`/users/${id}`, {
method: 'PATCH',
body: { name: 'Jane' }
})
// DELETE
await authFetch(`/users/${id}`, { method: 'DELETE' })
Composable на сущность
Для каждой сущности создаём composable с типизированными методами.
// app/composables/useOrders.ts
export function useOrders() {
const authFetch = useAuthFetch()
return {
getAll: (params?: OrderParams) =>
authFetch<PaginatedResponse<Order>>('/orders', { query: params }),
getById: (id: string) =>
authFetch<Order>(`/orders/${id}`),
create: (data: CreateOrderDto) =>
authFetch<Order>('/orders', { method: 'POST', body: data }),
update: (id: string, data: UpdateOrderDto) =>
authFetch<Order>(`/orders/${id}`, { method: 'PATCH', body: data }),
delete: (id: string) =>
authFetch(`/orders/${id}`, { method: 'DELETE' }),
// Доменные методы
changeStatus: (id: string, status: OrderStatus) =>
authFetch<Order>(`/orders/${id}/status`, { method: 'PATCH', body: { status } }),
}
}
Использование
<script setup lang="ts">
const ordersApi = useOrders()
const orders = ref<Order[]>([])
async function loadOrders() {
const response = await ordersApi.getAll({ page: 1 })
orders.value = response.data
}
async function handleCreate(data: CreateOrderDto) {
await ordersApi.create(data)
await loadOrders()
}
</script>
Обработка ошибок
import { FetchError } from 'ofetch'
async function handleSubmit() {
try {
await ordersApi.create(formData)
// success
} catch (error) {
if (error instanceof FetchError) {
if (error.statusCode === 422) {
// Ошибки валидации
validationErrors.value = error.data?.errors
} else if (error.statusCode === 409) {
// Конфликт
toast.error('Запись уже существует')
} else {
toast.error('Ошибка сервера')
}
}
}
}
Типы
// types module или app/types/Api.ts
export interface PaginatedResponse<T> {
data: T[]
meta: {
total: number
page: number
perPage: number
lastPage: number
}
}
export interface ApiError {
message: string
errors?: Record<string, string[]>
}
useAuthFetch (из auth-layer)
// auth-layer/composables/useAuthFetch.ts
export function useAuthFetch() {
const { token, refreshToken, logout } = useSession()
const config = useRuntimeConfig()
return $fetch.create({
baseURL: config.public.apiBase,
onRequest({ options }) {
if (token.value) {
options.headers = {
...options.headers,
Authorization: `Bearer ${token.value}`,
}
}
},
async onResponseError({ response }) {
if (response.status === 401) {
const refreshed = await refreshToken()
if (!refreshed) {
logout()
}
}
},
})
}
Чек-лист
- Публичные запросы через
$fetch - Авторизованные через
useAuthFetch() - Типизация response (
authFetch<User[]>(...)) - Composable на сущность для переиспользования
- Обработка ошибок (422, 401, etc.)