Files
nextjs-template/ai/nextjs-style-guide/data/rest/clients/manual.md
S.Gromov f2358da397 docs: добавить стайлгайд nextjs-style-guide в репозиторий
- Добавлена документация SLM-архитектуры, базовых правил и прикладных разделов
- Добавлены разделы: стили, SVG-спрайты, шаблоны генерации, PostCSS, REST, Realtime
- Удалены устаревшие файлы (спрайты, скрипты, стили из app/)
2026-04-30 19:32:10 +03:00

6.5 KiB
Raw Blame History

title, description, keywords
title description keywords
Ручное создание Создание REST-клиента вручную, когда OpenAPI нет или он неполный.
rest
ручной клиент
fetch
methods
dto
errors
infrastructure

Ручное создание

Ручной REST-клиент используется, когда у API нет OpenAPI-спецификации или она недостаточно точная для автогенерации.

Задача ручного клиента — дать такую же точку входа, как у автогенерированного клиента: именованный API-объект, методы по сущностям, DTO и GET-хуки рядом с клиентом.

Что нужно создать

src/infrastructure/
└── pet-project-api/
    ├── methods/
    │   └── posts.ts
    ├── hooks/
    │   └── index.ts
    ├── types/
    │   ├── client.ts
    │   ├── post.ts
    │   └── index.ts
    ├── errors/
    │   └── pet-project-api.error.ts
    ├── client.ts
    └── index.ts
Файл Роль
client.ts Базовый транспорт и создание инстанса клиента
methods/ Методы API по сущностям
types/ DTO запросов, ответов и типы клиента
errors/ Ошибки конкретного API
hooks/ GET-хуки REST-клиента, если данные нужны в Client Components
index.ts Публичный API REST-модуля

DTO и типы API

DTO запросов и ответов живут в types/. client.ts не содержит DTO и доменные типы.

// src/infrastructure/pet-project-api/types/post.ts
export type PostDto = {
  id: string
  slug: string
  title: string
}

export type PostListQueryDto = {
  limit?: number
  category?: string
}
// src/infrastructure/pet-project-api/types/index.ts
export type { PostDto, PostListQueryDto } from './post'

Типы, которые нужны только базовому транспорту, можно держать отдельно:

// src/infrastructure/pet-project-api/types/client.ts
export type QueryParams = Record<string, string | number | boolean>

Ошибка API

Ошибка API тоже относится к REST-модулю.

// src/infrastructure/pet-project-api/errors/pet-project-api.error.ts
export class PetProjectApiError extends Error {
  constructor(
    public readonly status: number,
    message: string,
  ) {
    super(message)
    this.name = 'PetProjectApiError'
  }
}

Базовый клиент

client.ts содержит только транспортную оболочку и сборку инстанса. Прямой fetch живёт здесь, а не в компонентах и не в методах верхних слоёв.

// src/infrastructure/pet-project-api/client.ts
import { PetProjectApiError } from './errors/pet-project-api.error'
import type { QueryParams } from './types/client'

export class PetProjectApiClient {
  constructor(
    private readonly baseUrl: string,
    private readonly defaultHeaders: Record<string, string> = {},
  ) {}

  async get<T>(path: string, params: QueryParams = {}): Promise<T> {
    const base = `${this.baseUrl.replace(/\/+$/, '')}/`
    const url = new URL(path.replace(/^\/+/, ''), base)

    Object.entries(params).forEach(([key, value]) => {
      url.searchParams.set(key, String(value))
    })

    const response = await fetch(url, {
      headers: {
        Accept: 'application/json',
        ...this.defaultHeaders,
      },
    })

    if (!response.ok) {
      throw new PetProjectApiError(response.status, response.statusText)
    }

    return response.json() as Promise<T>
  }
}

Это минимальный шаблон. Авторизация, дополнительные заголовки, next.revalidate, post, formdata и другие детали добавляются только когда они реально нужны API.

Методы API

Методы группируются по сущностям в methods/. Они не знают про React, SWR и UI.

// src/infrastructure/pet-project-api/methods/posts.ts
import type { PetProjectApiClient } from '../client'
import type { PostDto, PostListQueryDto } from '../types/post'

export function postsMethods(client: PetProjectApiClient) {
  return {
    /** GET /posts */
    list: (query: PostListQueryDto = {}) =>
      client.get<PostDto[]>('posts', query),

    /** GET /posts/{slug} */
    get: (slug: string) =>
      client.get<PostDto>(`posts/${slug}`),
  }
}

Метод возвращает DTO в форме API. Если данным нужен доменный смысл, маппинг делается выше, в business/.

Публичный API

index.ts собирает именованный API-объект и открывает наружу только публичные части модуля.

// src/infrastructure/pet-project-api/index.ts
import { PetProjectApiClient } from './client'
import { postsMethods } from './methods/posts'

const client = new PetProjectApiClient(
  process.env.NEXT_PUBLIC_API_URL ?? '',
  { 'Content-Type': 'application/json' },
)

export const petProjectApi = {
  posts: postsMethods(client),
}

export { PetProjectApiError } from './errors/pet-project-api.error'
export type { PostDto, PostListQueryDto } from './types'
export * from './hooks'

Внешний код импортирует только из infrastructure/pet-project-api, не из внутренних файлов модуля.

Правила

  • fetch используется только внутри базового клиента.
  • DTO запросов и ответов живут в types/.
  • client.ts не содержит DTO, GET-хуки и бизнес-логику.
  • Методы лежат в methods/ и возвращают DTO.
  • GET-хуки добавляются отдельно в hooks/, если данные нужны в Client Components.
  • Доменные типы и маппинг DTO живут не в REST-клиенте, а в business/.

Следующий шаг: GET-хуки REST-клиента или Стратегии получения данных.