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

Как проверяешь данные с бэкенда?

1.8 Middle🔥 141 комментариев
#JavaScript Core

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

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

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

Как проверить данные с бэкенда

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

1. Zod (современный стандарт)

Это самый популярный выбор для фронтенда на TypeScript:

// lib/validation.ts
import { z } from 'zod';

export const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(1).max(100),
  email: z.string().email(),
  age: z.number().int().positive().optional(),
  role: z.enum(['user', 'admin']),
  createdAt: z.string().datetime()
});

export type User = z.infer<typeof UserSchema>;

// В компоненте
import { UserSchema } from '@/lib/validation';

async function fetchUser(id: string) {
  try {
    const response = await fetch(`/api/users/${id}`);
    const rawData = await response.json();
    
    // Валидировать данные
    const user = UserSchema.parse(rawData);
    return user; // Теперь точно типизировано
  } catch (error) {
    if (error instanceof z.ZodError) {
      console.error('Ошибка валидации:', error.errors);
      throw new Error('Некорректные данные от API');
    }
    throw error;
  }
}

// Использование
const user = await fetchUser('123');
console.log(user.name); // TypeScript знает, что это строка

2. Type Guards (для простых проверок)

Если не хочешь добавлять Zod, используй type guards:

// Проверка конкретного типа
function isUser(data: unknown): data is User {
  if (typeof data !== 'object' || data === null) return false;
  
  const obj = data as Record<string, unknown>;
  return (
    typeof obj.id === 'string' &&
    typeof obj.name === 'string' &&
    typeof obj.email === 'string' &&
    typeof obj.role === 'string' &&
    ['user', 'admin'].includes(obj.role)
  );
}

// Использование
const user = await fetch('/api/users/123').then(r => r.json());
if (isUser(user)) {
  // TypeScript теперь знает, что user это User
  console.log(user.name);
} else {
  throw new Error('Invalid user data');
}

3. Pydantic + FastAPI (интеграция с бэкендом)

Используй то же самое на фронте, что и на бэкенде. На FastAPI бэкенде:

# backend/app/schemas.py
from pydantic import BaseModel, EmailStr, Field
from datetime import datetime

class UserResponse(BaseModel):
    id: str
    name: str = Field(..., min_length=1, max_length=100)
    email: EmailStr
    role: str = Literal['user', 'admin']
    created_at: datetime

Фронтенд должен отражать эту же структуру:

// frontend/lib/schemas.ts
const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(1).max(100),
  email: z.string().email(),
  role: z.enum(['user', 'admin']),
  created_at: z.string().datetime()
});

4. API оборачивание с автоматической валидацией

Создай обёртку для всех API запросов:

// lib/api.ts
import { z } from 'zod';

export async function apiCall<T>(
  url: string,
  schema: z.ZodSchema<T>,
  options?: RequestInit
): Promise<T> {
  try {
    const response = await fetch(url, {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        ...options?.headers
      }
    });
    
    if (!response.ok) {
      throw new Error(`API error: ${response.status}`);
    }
    
    const data = await response.json();
    
    // Автоматическая валидация
    return schema.parse(data);
  } catch (error) {
    if (error instanceof z.ZodError) {
      console.error('Validation failed:', error);
      throw new Error('Invalid response from server');
    }
    throw error;
  }
}

// Использование
const user = await apiCall(
  '/api/users/123',
  UserSchema
);

5. Проверка в компонентах (React)

В самом компоненте добавь защиту:

'use client';

import { useEffect, useState } from 'react';
import { UserSchema, type User } from '@/lib/validation';

export function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState<User | null>(null);
  const [error, setError] = useState<string | null>(null);
  
  useEffect(() => {
    async function load() {
      try {
        const response = await fetch(`/api/users/${userId}`);
        const data = await response.json();
        
        // Валидация перед состоянием
        const validatedUser = UserSchema.parse(data);
        setUser(validatedUser);
      } catch (err) {
        setError('Failed to load user');
      }
    }
    
    load();
  }, [userId]);
  
  if (error) return <div>Error: {error}</div>;
  if (!user) return <div>Loading...</div>;
  
  return <div>
    <h1>{user.name}</h1>
    <p>{user.email}</p>
  </div>;
}

6. Обработка ошибок от API

От бэкенда может прийти ошибка - её тоже нужно парсить:

const ErrorResponseSchema = z.object({
  error: z.object({
    code: z.string(),
    message: z.string(),
    details: z.record(z.string(), z.any()).optional()
  })
});

async function apiCall(url: string, options?: RequestInit) {
  const response = await fetch(url, options);
  const data = await response.json();
  
  if (!response.ok) {
    try {
      const error = ErrorResponseSchema.parse(data);
      throw new Error(error.error.message);
    } catch {
      throw new Error('Unknown error from server');
    }
  }
  
  return data;
}

Best Practices для валидации

  1. Всегда валидируй данные от API, даже от своего же сервера
  2. Используй Zod для продакшена - это инвестиция в качество
  3. Типизируй результаты валидации (z.infer)
  4. Обёртывай все API запросы в одной функции
  5. Обрабатывай ошибки валидации отдельно от сетевых ошибок
  6. Логируй ошибки валидации для дебага
  7. Синхронизируй схемы фронта и бэкенда
// Полный пример обёртки
export async function fetchData<T>(
  url: string,
  schema: z.ZodSchema<T>
): Promise<Result<T>> {
  try {
    const response = await fetch(url);
    if (!response.ok) return { ok: false, error: 'Network error' };
    
    const raw = await response.json();
    const validated = schema.safeParse(raw);
    
    if (!validated.success) {
      return { ok: false, error: 'Invalid data' };
    }
    
    return { ok: true, data: validated.data };
  } catch (error) {
    return { ok: false, error: String(error) };
  }
}

Это критический навык, который показывает понимание надёжности и безопасности фронтенда!

Как проверяешь данные с бэкенда? | PrepBro