GraphQL vs REST в TypeScript: дуэль титанов или выбор под задачу?
Когда вы пишете на TypeScript, вопрос архитектуры бэкенда встаёт особенно остро. Хочется не просто "чтобы работало", а чтобы типы летали, ошибки отлавливались на этапе компиляции, а код был предсказуемым. И тут перед разработчиком встаёт извечный выбор: проверенный временем REST или модный, гибкий GraphQL? Обе технологии отлично дружат с TypeScript, но каждая диктует свой стиль разработки.
В этой статье мы не будем лить воду и рассказывать прописные истины. Мы разберём GraphQL vs REST в TypeScript на конкретных примерах, с кодом, узкими местами и сценариями, где один подход убивает другой. Поехали!
REST: старый друг, который не подводит
REST (Representational State Transfer) — это архитектурный стиль, где каждый ресурс (пользователь, пост, комментарий) имеет свой URL. Вы стучитесь на /api/users/1 и получаете пользователя. Просто и предсказуемо. В TypeScript проектах REST часто реализуют через Express, Fastify или NestJS.
Типизация в REST с TypeScript
Главная боль REST — слабая связь между бэкендом и фронтендом. Вы отправляете JSON, а фронтенд гадает: "А что там пришло?". TypeScript спасает через общие типы или OpenAPI (Swagger).
Пример: бэкенд на Express с TypeScript
import express, { Request, Response } from 'express';
interface User {
id: number;
name: string;
email: string;
}
const app = express();
app.get('/api/users/:id', (req: Request, res: Response) => {
const user: User = { id: +req.params.id, name: 'Alice', email: 'alice@example.com' };
res.json(user);
});
Фронтенд (fetch):
interface User {
id: number;
name: string;
email: string;
}
async function getUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`);
return res.json() as Promise<User>;
}
Проблема: если бэкенд изменит структуру User, фронтенд узнает об этом только в рантайме. Типы на фронте — это просто копия. Решение: генерация типов из OpenAPI-спецификации.
Плюсы REST в TypeScript
- Простота кэширования: HTTP-кэш (CDN, браузер) работает из коробки.
- Чёткая структура: URL = ресурс, методы = действия (GET, POST, PUT, DELETE).
- Широкая поддержка: Любой клиент (браузер, curl, Postman) понимает REST.
- Низкий порог входа: Даже джуниор разберётся за час.
Минусы REST
- Over-fetching и Under-fetching: Вы либо тащите лишние поля, либо делаете несколько запросов.
- Жёсткая структура: Чтобы получить пользователя + его посты, нужно два запроса или костыли (поле expand).
- Слабая типизация: Без OpenAPI типы на клиенте — это просто "честное слово".
GraphQL: гибкость и контроль
GraphQL — это язык запросов, который даёт клиенту право самому решать, какие данные получать. Один эндпоинт (/graphql), один запрос — и вы получаете именно то, что заказали. TypeScript и GraphQL — идеальная пара: типы генерируются автоматически из схемы.
Типизация в GraphQL с TypeScript
Сначала пишется схема (SDL):
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
}
type Query {
user(id: ID!): User
}
Затем генерируются типы (через graphql-codegen):
// generated.ts
export type User = {
__typename?: 'User';
id: string;
name: string;
email: string;
posts: Array<Post>;
};
export type Post = {
__typename?: 'Post';
id: string;
title: string;
content: string;
};
Клиентский запрос (Apollo Client):
import { gql, useQuery } from '@apollo/client';
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
name
email
posts {
title
}
}
}
`;
function UserProfile({ id }: { id: string }) {
const { data, loading } = useQuery(GET_USER, { variables: { id } });
if (loading) return <div>Loading...</div>;
return (
<div>
<h3>{data?.user.name}</h3>
<ul>
{data?.user.posts.map(post => <li key={post.title}>{post.title}</li>)}
</ul>
</div>
);
}
Заметьте: мы запросили только name, email и title постов — никакого оверфетчинга. Типы сгенерированы, всё типобезопасно.
Плюсы GraphQL в TypeScript
- Точные данные: Клиент получает ровно то, что нужно. Ни байта лишнего.
- Один запрос вместо N: Вложенные ресурсы (пользователь → посты → комментарии) выбираются за один раунд.
- Автоматическая типизация: Codegen синхронизирует бэкенд и фронтенд. Изменили схему — типы обновились везде.
- Интроспекция: Документация живёт в схеме, GraphiQL — ваш лучший друг.
Минусы GraphQL
- Сложное кэширование: HTTP-кэш не работает (все запросы на один URL). Нужны специализированные решения (Apollo cache, Relay).
- Проблемы с производительностью: Клиент может запросить огромное вложенное дерево — бэкенд упадёт (нужны лимиты глубины и сложности).
- Более крутой learning curve: Ресолверы, схемы, N+1 проблема — нужно разбираться.
- Избыточность для простых API: Если у вас 3 эндпоинта — GraphQL оверхед.
GraphQL vs REST в TypeScript: когда что выбирать
Давайте честно: сравнение GraphQL и REST — это не битва на смерть, а выбор инструмента под задачу. Вот чек-лист:
Выбирайте REST, если:
- У вас простое CRUD-приложение (блог, новости).
- Вы активно используете HTTP-кэш (CDN, браузерный кэш).