GraphQL vs REST в TypeScript: что выбрать для вашего проекта

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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, браузерный кэш).

Блоги

Рекомендаций по книгам