← Назад к вопросам
Как реализовать взаимодействие frontend и API?
1.0 Junior🔥 221 комментариев
#API и сетевые протоколы
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Взаимодействие Frontend и Backend API
Это ключевой аспект разработки — правильно организованная коммуникация между фронтенд и бэкенд частями приложения. Рассмотрю полный цикл от проектирования до production.
1. Проектирование API (договор между сторонами)
Определение эндпоинтов
// API Specification (OpenAPI/Swagger)
// Это договор между frontend и backend
interface APIContract {
// GET - получение данных
'GET /api/v1/users': {
query: { limit?: number; offset?: number; search?: string };
response: {
data: User[];
total: number;
hasMore: boolean;
};
};
// GET с ID
'GET /api/v1/users/:id': {
params: { id: string };
response: User | { error: string };
};
// POST - создание
'POST /api/v1/users': {
body: { name: string; email: string };
response: User | { error: string };
};
// PUT - обновление
'PUT /api/v1/users/:id': {
params: { id: string };
body: Partial<User>;
response: User | { error: string };
};
// DELETE - удаление
'DELETE /api/v1/users/:id': {
params: { id: string };
response: { success: boolean };
};
}
Swagger документация
# Автоматическая генерация Swagger из NestJS
# frontend разработчик открывает http://localhost:3000/api/docs
# и видит все эндпоинты с примерами
// In NestJS
import { ApiOperation, ApiResponse } from '@nestjs/swagger';
@Controller('users')
export class UsersController {
@Get()
@ApiOperation({ summary: 'Get all users' })
@ApiResponse({
status: 200,
description: 'List of users',
type: [UserDto],
})
async getAll() {
return this.usersService.findAll();
}
}
2. Frontend запрос к API
Базовая структура (React/Next.js)
// hooks/useUsers.ts
import { useState, useEffect } from 'react';
interface User {
id: string;
name: string;
email: string;
}
export function useUsers() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchUsers = async () => {
try {
setLoading(true);
// Запрос к backend API
const response = await fetch(
'http://localhost:3000/api/v1/users?limit=10',
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getToken()}`,
},
}
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const { data } = await response.json();
setUsers(data);
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
setUsers([]);
} finally {
setLoading(false);
}
};
fetchUsers();
}, []);
return { users, loading, error };
}
// Component
export function UsersList() {
const { users, loading, error } = useUsers();
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
API Client с Axios (лучший подход)
// api/client.ts
import axios, { AxiosInstance } from 'axios';
class APIClient {
private client: AxiosInstance;
constructor() {
this.client = axios.create({
baseURL: process.env.REACT_APP_API_URL || 'http://localhost:3000',
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
// Автоматически добавляем token в каждый запрос
this.client.interceptors.request.use(
(config) => {
const token = localStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// Обработка ошибок
this.client.interceptors.response.use(
(response) => response.data,
(error) => {
if (error.response?.status === 401) {
// Unauthorized - refresh token или logout
window.location.href = '/login';
}
return Promise.reject(error);
}
);
}
// Methods
async getUsers(limit = 10, offset = 0) {
return this.client.get('/api/v1/users', {
params: { limit, offset },
});
}
async getUser(id: string) {
return this.client.get(`/api/v1/users/${id}`);
}
async createUser(data: { name: string; email: string }) {
return this.client.post('/api/v1/users', data);
}
async updateUser(id: string, data: Partial<User>) {
return this.client.put(`/api/v1/users/${id}`, data);
}
async deleteUser(id: string) {
return this.client.delete(`/api/v1/users/${id}`);
}
}
export const apiClient = new APIClient();
3. Backend обработка запроса
// users.controller.ts
import { Controller, Get, Post, Put, Delete, Body, Param, Query } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto, UpdateUserDto } from './dto';
@Controller('api/v1/users')
export class UsersController {
constructor(private usersService: UsersService) {}
// GET /api/v1/users?limit=10&offset=0
@Get()
async getAll(
@Query('limit') limit: number = 10,
@Query('offset') offset: number = 0,
) {
const [data, total] = await this.usersService.findAll(limit, offset);
return {
data,
total,
hasMore: offset + limit < total,
};
}
// GET /api/v1/users/:id
@Get(':id')
async getOne(@Param('id') id: string) {
return this.usersService.findOne(id);
}
// POST /api/v1/users
@Post()
async create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
// PUT /api/v1/users/:id
@Put(':id')
async update(
@Param('id') id: string,
@Body() updateUserDto: UpdateUserDto,
) {
return this.usersService.update(id, updateUserDto);
}
// DELETE /api/v1/users/:id
@Delete(':id')
async delete(@Param('id') id: string) {
await this.usersService.delete(id);
return { success: true };
}
}
4. Обработка ошибок и валидация
Backend валидация
// dto/create-user.dto.ts
import { IsEmail, MinLength, IsNotEmpty } from 'class-validator';
export class CreateUserDto {
@IsNotEmpty()
@MinLength(3)
name: string;
@IsNotEmpty()
@IsEmail()
email: string;
}
// Автоматическая валидация в контроллере
@Post()
@UsePipes(new ValidationPipe()) // Валидация DTO
async create(@Body() createUserDto: CreateUserDto) {
// Если DTO невалидна, NestJS автоматически вернёт 400 с ошибками
return this.usersService.create(createUserDto);
}
Frontend обработка ошибок
// api/client.ts
this.client.interceptors.response.use(
(response) => response.data,
(error) => {
if (error.response?.status === 400) {
// Validation error
const { message, errors } = error.response.data;
console.error('Validation errors:', errors);
// Показать пользователю
return Promise.reject(new ValidationError(errors));
}
if (error.response?.status === 401) {
// Unauthorized
redirectToLogin();
}
if (error.response?.status === 500) {
// Server error
showErrorNotification('Server error. Try again later');
}
return Promise.reject(error);
}
);
5. Аутентификация и авторизация
Backend
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(private jwtService: JwtService) {}
async login(email: string, password: string) {
const user = await this.validateUser(email, password);
if (!user) {
throw new UnauthorizedException('Invalid credentials');
}
// Генерируем токен
const token = this.jwtService.sign({
sub: user.id,
email: user.email,
});
return { access_token: token };
}
}
// Guard для защиты эндпоинтов
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(private jwtService: JwtService) {}
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const authHeader = request.headers.authorization;
if (!authHeader) {
throw new UnauthorizedException('No token provided');
}
const [, token] = authHeader.split(' ');
try {
const payload = this.jwtService.verify(token);
request.user = payload;
return true;
} catch {
throw new UnauthorizedException('Invalid token');
}
}
}
// Использование
@Controller('api/v1/users')
@UseGuards(JwtAuthGuard)
export class UsersController {
@Get('profile')
getProfile(@Request() req) {
return this.usersService.findOne(req.user.sub);
}
}
Frontend
// hooks/useAuth.ts
export function useAuth() {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Проверяем есть ли токен при загрузке
const token = localStorage.getItem('authToken');
if (token) {
// Проверяем валидность токена
apiClient.getProfile()
.then(setUser)
.catch(() => logout());
}
setLoading(false);
}, []);
const login = async (email: string, password: string) => {
const { access_token } = await apiClient.login(email, password);
localStorage.setItem('authToken', access_token);
const user = await apiClient.getProfile();
setUser(user);
};
const logout = () => {
localStorage.removeItem('authToken');
setUser(null);
};
return { user, loading, login, logout, isAuthenticated: !!user };
}
6. CORS (Cross-Origin Resource Sharing)
// Backend конфигурация
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// CORS для development
app.enableCors({
origin: process.env.FRONTEND_URL || 'http://localhost:3001',
credentials: true, // Для cookies
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
});
await app.listen(3000);
}
bootstrap();
7. Полный цикл запроса
// 1. Frontend делает запрос
const response = await apiClient.getUsers();
// 2. API Client добавляет headers и token
// GET /api/v1/users
// Headers: Authorization: Bearer token123
// 3. Backend получает запрос
// Middleware проверяет CORS
// Guard проверяет авторизацию
// Pipe валидирует параметры
// 4. Controller вызывает Service
const users = await this.usersService.findAll(limit, offset);
// 5. Service работает с БД
const [data, total] = await this.repository.findAndCount(...);
// 6. Возвращаем ответ
return { data, total, hasMore };
// 7. Frontend получает ответ
// Обновляет state
setUsers(response.data);
8. Лучшие практики
interface BestPractices {
// Версионирование API
urls: [
'/api/v1/users', // Текущая версия
'/api/v2/users', // Новая версия (обратная совместимость)
];
// Консистентные коды ошибок
errors: {
400: 'Bad Request - Validation error',
401: 'Unauthorized - No token or invalid',
403: 'Forbidden - No permission',
404: 'Not Found - Resource not exists',
500: 'Internal Server Error',
};
// Структура ответа
response: {
success: true,
data: {},
error?: null,
timestamp: '2024-01-01T10:00:00Z',
};
// Pagination для больших наборов
pagination: {
limit: 10,
offset: 0,
total: 100,
hasMore: true,
};
// Rate limiting
rateLimiting: '100 requests per minute',
}
Взаимодействие frontend и backend — это искусство согласования контрактов через API. Основная идея: четко определить что передаёт фронтенд, что возвращает бэкенд, и оба должны строго придерживаться этого контракта.