Введение: API — это не просто запросы, это диалог
Когда-то давно, в эпоху монолитов, разработчики писали бэкенд на PHP или Java, а фронтенд просто дёргал готовые URL. Всё было просто, но медленно. Сегодня экосистема TypeScript диктует новые правила: мы хотим не просто обмениваться данными, а делать это безопасно, строго и с автодополнением. Именно здесь в игру вступают три гиганта: REST, GraphQL и gRPC.
В этой статье мы не будем пересказывать документацию. Мы пройдёмся по каждому протоколу с точки зрения практика на TypeScript: увидим реальные примеры кода, сравним производительность и поймём, какой инструмент подходит для вашего проекта. Если вы пишете на TypeScript и хотите прокачать свои навыки работы с API — вы по адресу.
REST API на TypeScript: классика, которая не умирает
REST (Representational State Transfer) — это стандарт де-факто. Простота, HTTP-методы (GET, POST, PUT, DELETE) и ресурсы. Но в TypeScript REST раскрывается по-новому: мы получаем строгую типизацию запросов и ответов.
Как выглядит типизированный REST клиент
Давайте напишем минимальный, но рабочий клиент для REST API на TypeScript. Без лишних библиотек — только fetch и типы.
// Типы для ответа
interface User {
id: number;
name: string;
email: string;
}
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
// Типизированный клиент
class RestClient {
private baseUrl: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
async get<T>(endpoint: string): Promise<ApiResponse<T>> {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: T = await response.json();
return {
data,
status: response.status,
message: 'Success',
};
}
async post<T, U>(endpoint: string, body: U): Promise<ApiResponse<T>> {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: T = await response.json();
return {
data,
status: response.status,
message: 'Created',
};
}
}
// Использование
const client = new RestClient('https://jsonplaceholder.typicode.com');
async function fetchUser() {
const response = await client.get<User>('/users/1');
console.log(response.data.name); // Автодополнение работает!
}
fetchUser();
Как видите, REST API TypeScript даёт нам контроль над типами на каждом этапе. Ошибка в названии поля? TypeScript поймает её на этапе компиляции.
Когда выбирать REST
- Простота — REST легко понять и поддерживать.
- Кэширование — HTTP-кэш работает из коробки.
- CRUD-приложения — блоги, магазины, админки.
Минус: при сложных вложенных запросах вы либо получаете много данных (over-fetching), либо делаете N запросов (N+1 problem).
GraphQL на TypeScript: гибкость и точность
GraphQL — это язык запросов, созданный Facebook. Его главная фишка: вы сами решаете, какие поля получить. GraphQL TypeScript — это мощный тандем, который даёт полную типовую безопасность на клиенте и сервере.
Пишем GraphQL-запрос с типизацией
Для примера используем библиотеку graphql-request — лёгкий клиент для TypeScript.
import { request, gql } from 'graphql-request';
// Определяем типы
interface User {
id: string;
name: string;
posts: Post[];
}
interface Post {
id: string;
title: string;
}
// GraphQL-запрос с переменными
const GET_USER_QUERY = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
posts {
id
title
}
}
}
`;
async function fetchUserWithPosts(userId: string): Promise<User> {
const endpoint = 'https://api.example.com/graphql';
const data = await request<{ user: User }>(endpoint, GET_USER_QUERY, { id: userId });
return data.user;
}
// Использование
fetchUserWithPosts('1').then(user => {
console.log(user.name); // "Leanne Graham"
console.log(user.posts.length); // Только то, что запросили
});
Обратите внимание: мы получаем ровно те поля, которые указали в запросе. Никакого лишнего веса. А с TypeScript мы гарантируем, что структура ответа совпадает с ожидаемой.
Серверная часть на TypeScript
GraphQL-сервер на TypeScript с использованием type-graphql — это элегантный код с декораторами.
import { Resolver, Query, Arg, ObjectType, Field, ID } from 'type-graphql';
@ObjectType()
class User {
@Field(type => ID)
id: string;
@Field()
name: string;
@Field(type => [Post])
posts: Post[];
}
@ObjectType()
class Post {
@Field(type => ID)
id: string;
@Field()
title: string;
}
@Resolver(of => User)
class UserResolver {
@Query(returns => User)
async user(@Arg('id') id: string): Promise<User> {
// Логика получения данных из БД
return { id, name: 'Leanne', posts: [] };
}
}
GraphQL TypeScript идеален для сложных интерфейсов с разнородными данными. Минус: сложность кэширования и более высокая нагрузка на сервер при плохо написанных резолверах.
gRPC на TypeScript: скорость и бинарный протокол
gRPC — это детище Google. Он использует Protocol Buffers (protobuf) для сериализации и HTTP/2 для передачи. gRPC TypeScript — выбор для микросервисов и high-load систем.
Определяем контракт и генерируем код
Сначала создаём .proto файл:
syntax = "proto3";
service UserService {
rpc GetUser (GetUserRequest) returns (UserResponse);
}
message GetUserRequest {
int32 id = 1;
}
message UserResponse {
int32 id = 1;
string name = 2;
string email = 3;
}
Затем генерируем TypeScript-типы с помощью protoc и плагина protoc-gen-ts.
Клиент на TypeScript
После генерации кода работаем с ним как с обычными классами:
import * as grpc from