← Назад к вопросам
Как можно построить 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);
});
Такой подход обеспечивает масштабируемость, тестируемость и поддерживаемость приложения.