← Назад к вопросам

Как можно построить Web-приложение чтобы оно общалось с Backend при помощи REST API?

2.0 Middle🔥 211 комментариев
#Браузер и сетевые технологии

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Построение Web-приложения с REST API

Для интеграции фронтенда с бэкендом через REST API нужно продумать архитектуру, обработку ошибок и управление состоянием данных. Это критически важно для надёжного приложения.

Основной подход

1. Создание HTTP-клиента

Основная часть - обёртка над fetch или axios:

// lib/api.ts
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'https://api.example.com';

export const api = {
  async get(endpoint: string) {
    const response = await fetch(`${API_BASE_URL}${endpoint}`, {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
        ...(getAuthToken() && { 'Authorization': `Bearer ${getAuthToken()}` })
      }
    });
    return handleResponse(response);
  },

  async post(endpoint: string, data: unknown) {
    const response = await fetch(`${API_BASE_URL}${endpoint}`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        ...(getAuthToken() && { 'Authorization': `Bearer ${getAuthToken()}` })
      },
      body: JSON.stringify(data)
    });
    return handleResponse(response);
  },

  async put(endpoint: string, data: unknown) {
    // Аналогично POST
  },

  async delete(endpoint: string) {
    // Аналогично GET
  }
};

async function handleResponse(response: Response) {
  if (!response.ok) {
    const error = await response.json();
    throw new APIError(error.message, response.status);
  }
  return response.json();
}

2. Управление состоянием (State Management)

С React Hooks:

// hooks/useQuestions.ts
import { useState, useEffect } from 'react';
import { api } from '@/lib/api';

export function useQuestions() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const fetch = useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const questions = await api.get('/api/v1/questions');
      setData(questions);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    fetch();
  }, []);

  return { data, loading, error, refetch: fetch };
}

3. Использование в компонентах

// components/QuestionsList.tsx
export function QuestionsList() {
  const { data: questions, loading, error } = useQuestions();

  if (loading) return <div>Загрузка...</div>;
  if (error) return <div>Ошибка: {error}</div>;

  return (
    <ul>
      {questions?.map(q => (
        <li key={q.id}>{q.title}</li>
      ))}
    </ul>
  );
}

Архитектурные слои

Layer 1: API Client

// Низкоуровневое взаимодействие с HTTP
api.get('/api/v1/questions')

Layer 2: Service Layer

// Бизнес-логика
export const questionService = {
  async getQuestions(filters) {
    const questions = await api.get('/api/v1/questions');
    return questions.filter(q => {
      // фильтрация, трансформация
    });
  }
};

Layer 3: React Hooks

// Интеграция с React
export function useQuestions(filters) {
  const [data, setData] = useState();
  useEffect(() => {
    questionService.getQuestions(filters).then(setData);
  }, [filters]);
  return data;
}

Layer 4: Components

// Представление
export function QuestionsList() {
  const questions = useQuestions();
  return <div>{/* рендер */}</div>;
}

Обработка ошибок

// Глобальный обработчик ошибок
export class APIError extends Error {
  constructor(message: string, status: number) {
    super(message);
    this.status = status;
  }
}

// В компоненте
const handleSubmit = async (data) => {
  try {
    await api.post('/api/v1/questions', data);
    toast.success('Вопрос добавлен');
  } catch (error) {
    if (error.status === 401) {
      redirectToLogin();
    } else if (error.status === 400) {
      setValidationErrors(error.details);
    } else {
      toast.error('Неожиданная ошибка');
    }
  }
};

Сетевые оптимизации

1. Кэширование данных

const cache = new Map();

export async function getCachedData(key, fetcher) {
  if (cache.has(key)) {
    return cache.get(key);
  }
  const data = await fetcher();
  cache.set(key, data);
  return data;
}

2. Request Deduplication

const pendingRequests = new Map();

export async function api(url) {
  if (pendingRequests.has(url)) {
    return pendingRequests.get(url);
  }
  const promise = fetch(url);
  pendingRequests.set(url, promise);
  try {
    return await promise;
  } finally {
    pendingRequests.delete(url);
  }
}

3. Pagination

const [page, setPage] = useState(1);
const questions = await api.get(
  `/api/v1/questions?page=${page}&limit=20`
);

Аутентификация

// lib/auth.ts
export function setAuthToken(token: string) {
  localStorage.setItem('auth_token', token);
}

export function getAuthToken(): string | null {
  return localStorage.getItem('auth_token');
}

export function clearAuthToken() {
  localStorage.removeItem('auth_token');
}

// При каждом запросе токен автоматически добавляется

Environment Variables

# .env.local
NEXT_PUBLIC_API_URL=https://api.example.com

Тестирование

// __tests__/api.test.ts
import { vi } from 'vitest';

vi.stubGlobal('fetch', vi.fn());

test('should fetch questions', async () => {
  global.fetch.mockResolvedValueOnce({
    ok: true,
    json: () => Promise.resolve([{ id: 1, title: 'Q1' }])
  });

  const data = await api.get('/api/v1/questions');
  expect(data).toHaveLength(1);
});

Такой подход обеспечивает масштабируемость, тестируемость и поддерживаемость приложения.

Как можно построить Web-приложение чтобы оно общалось с Backend при помощи REST API? | PrepBro