2026-04-30 19:32:10 +03:00
---
title: Ручное создание
description: Создание REST-клиента вручную, когда OpenAPI нет или он неполный.
2026-05-08 08:21:34 +03:00
keywords: [rest, ручной клиент, fetch, methods, dto, errors, infra]
2026-04-30 19:32:10 +03:00
---
# Ручное создание
Ручной REST-клиент используется, когда у API нет OpenAPI-спецификации или она недостаточно точная для автогенерации.
Задача ручного клиента — дать такую же точку входа, как у автогенерированного клиента: именованный API-объект, методы по сущностям, DTO и GET-хуки рядом с клиентом.
## Что нужно создать
```text
2026-05-08 08:21:34 +03:00
src/infra/
2026-04-30 19:32:10 +03:00
└── 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 и доменные типы.
```ts
2026-05-08 08:21:34 +03:00
// src/infra/pet-project-api/types/post.ts
2026-04-30 19:32:10 +03:00
export type PostDto = {
id: string
slug: string
title: string
}
export type PostListQueryDto = {
limit?: number
category?: string
}
```
```ts
2026-05-08 08:21:34 +03:00
// src/infra/pet-project-api/types/index.ts
2026-04-30 19:32:10 +03:00
export type { PostDto, PostListQueryDto } from './post'
```
Типы, которые нужны только базовому транспорту, можно держать отдельно:
```ts
2026-05-08 08:21:34 +03:00
// src/infra/pet-project-api/types/client.ts
2026-04-30 19:32:10 +03:00
export type QueryParams = Record< string , string | number | boolean >
```
## Ошибка API
Ошибка API тоже относится к REST-модулю.
```ts
2026-05-08 08:21:34 +03:00
// src/infra/pet-project-api/errors/pet-project-api.error.ts
2026-04-30 19:32:10 +03:00
export class PetProjectApiError extends Error {
constructor(
public readonly status: number,
message: string,
) {
super(message)
this.name = 'PetProjectApiError'
}
}
```
## Базовый клиент
`client.ts` содержит только транспортную оболочку и сборку инстанса. Прямой `fetch` живёт здесь, а не в компонентах и не в методах верхних слоёв.
```ts
2026-05-08 08:21:34 +03:00
// src/infra/pet-project-api/client.ts
2026-04-30 19:32:10 +03:00
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.
```ts
2026-05-08 08:21:34 +03:00
// src/infra/pet-project-api/methods/posts.ts
2026-04-30 19:32:10 +03:00
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-объект и открывает наружу только публичные части модуля.
```ts
2026-05-08 08:21:34 +03:00
// src/infra/pet-project-api/index.ts
2026-04-30 19:32:10 +03:00
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'
```
2026-05-08 08:21:34 +03:00
Внешний код импортирует только из `infra/pet-project-api` , не из внутренних файлов модуля.
2026-04-30 19:32:10 +03:00
## Правила
- `fetch` используется только внутри базового клиента.
- DTO запросов и ответов живут в `types/` .
- `client.ts` не содержит DTO, GET-хуки и бизнес-логику.
- Методы лежат в `methods/` и возвращают DTO.
- GET-хуки добавляются отдельно в `hooks/` , если данные нужны в Client Components.
- Доменные типы и маппинг DTO живут не в REST-клиенте, а в `business/` .
Следующий шаг: [GET-хуки REST-клиента ](./hooks.md ) или [Стратегии получения данных ](../strategies/index.md ).