Короткий ответ: в TypeScript POST-запрос чаще всего отправляют через fetch. Отличие от JavaScript не в самом запросе, а в типизации данных: отдельно описываем тело запроса, ответ сервера и ошибку, которую может вернуть API
Минимальный пример:
async function createUser() {
const response = await fetch("https://example.com/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Dinar",
email: "dinar@example.com",
}),
});
return response.json();
}
Такой код работает, но TypeScript пока почти ничего не знает о форме данных. Для реального проекта лучше добавить типы
Типизируем тело запроса
type CreateUserRequest = {
name: string;
email: string;
};
type User = {
id: number;
name: string;
email: string;
};
Теперь функция:
async function createUser(data: CreateUserRequest): Promise<User> {
const response = await fetch("https://example.com/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
return response.json() as Promise<User>;
}
Вызов:
const user = await createUser({
name: "Dinar",
email: "dinar@example.com",
});
console.log(user.id);
Если забыть email, TypeScript покажет ошибку еще до запуска
Обработка ошибок API
У многих API ошибка тоже приходит JSON-объектом:
type ApiError = {
message: string;
code?: string;
};
Тогда можно аккуратно разобрать ответ:
async function createUser(data: CreateUserRequest): Promise<User> {
const response = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
if (!response.ok) {
const error = (await response.json()) as ApiError;
throw new Error(error.message);
}
return response.json() as Promise<User>;
}
Главная мысль: fetch не бросает исключение при HTTP-статусе 400 или 500. Нужно проверять response.ok
POST с авторизацией
Если API принимает токен:
async function createUser(data: CreateUserRequest, token: string): Promise<User> {
const response = await fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json() as Promise<User>;
}
Не храните секретные серверные токены во frontend-коде. В браузере может быть только пользовательский токен, который допустимо отдавать клиенту
Универсальный helper
Если POST-запросов много, вынесите helper:
async function postJson<TResponse, TBody>(url: string, body: TBody): Promise<TResponse> {
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json() as Promise<TResponse>;
}
Использование:
const user = await postJson<User, CreateUserRequest>("/api/users", {
name: "Dinar",
email: "dinar@example.com",
});
Частые ошибки
Первая ошибка — забыть JSON.stringify. В body нельзя передать обычный объект как JSON без преобразования
Вторая ошибка — забыть заголовок Content-Type: application/json
Третья ошибка — считать, что fetch сам бросит ошибку на статусе 500
Четвертая ошибка — писать any для ответа. Тогда TypeScript перестает помогать именно там, где чаще всего ломаются интеграции
Пятая ошибка — не различать тип запроса и тип ответа. У создания пользователя request может не иметь id, а response уже должен иметь id
Самопроверка
Создайте тип CreateUserRequest, специально уберите поле email из вызова и убедитесь, что TypeScript показывает ошибку. Затем подмените адрес на неверный и проверьте, что код попадает в ветку !response.ok
Что почитать дальше по TypeScript
Если нужен общий маршрут по теме, откройте рубрику TypeScript. Для соседних задач пригодятся эти разборы:
- 10 вопросов по TypeScript и ответы на них
- Any, unknown, never и strict
- App.tsx: что это за файл и как добавить TypeScript в React
- Conditional types в TypeScript: как работает extends



