- Добавлена документация SLM-архитектуры, базовых правил и прикладных разделов - Добавлены разделы: стили, SVG-спрайты, шаблоны генерации, PostCSS, REST, Realtime - Удалены устаревшие файлы (спрайты, скрипты, стили из app/)
6.5 KiB
title, description, keywords
| title | description | keywords | |||||||
|---|---|---|---|---|---|---|---|---|---|
| Ручное создание | Создание REST-клиента вручную, когда OpenAPI нет или он неполный. |
|
Ручное создание
Ручной 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-клиента или Стратегии получения данных.