Стандарты

API запросы ($fetch.create)

Используем $fetch.create() для создания преконфигурированного HTTP-клиента.

Используем $fetch.create() для создания преконфигурированного HTTP-клиента.

Документация Nuxt:

Подход

  • Публичные запросы$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.)

Связанные стандарты