В мире современной веб-разработки связка высокопроизводительного бэкенда на Python и динамичного фронтенда на JavaScript стала золотым стандартом. FastAPI, с его невероятной скоростью, автоматической документацией и типизацией Python, идеально подходит для создания надежных API. С другой стороны, React остается неоспоримым королем в построении интерактивных, компонентных пользовательских интерфейсов.
Но как заставить их «общаться» друг с другом? Как обрабатывать стилизацию, управлять состоянием и избегать пресловутых ошибок CORS?
В этом исчерпывающем руководстве я проведу вас через весь процесс создания full-stack приложения с нуля: стильного, современного менеджера задач. Мы не просто напишем код; мы поймем, почему мы его пишем.
Наш план действий:
- Сначала бэкенд: Настроим мощный бэкенд на FastAPI для управления нашими задачами.
- Основа фронтенда: Создадим приложение на React с помощью Vite для молниеносной разработки.
- Стилизация с Tailwind CSS: Построим красивый, адаптивный интерфейс, не написав ни единой строчки кастомного CSS.
- Соединяем точки: Используем Axios, чтобы наш фронтенд и бэкенд бесшовно общались.
- Полировка приложения: Добавим состояния загрузки и обработку ошибок для профессионального пользовательского опыта.
Готовы? Давайте погружаться.
Часть 1: Создание API на FastAPI
Сначала построим двигатель нашего приложения.
1. Структура проекта и виртуальное окружение
Чистая структура — ключ к успеху. Создадим основную папку проекта и подпапку backend.
mkdir fastapi-react-tasker
cd fastapi-react-tasker
mkdir backend
cd backend
Внутри backend создадим виртуальное окружение Python. Это обязательная лучшая практика для изоляции зависимостей проекта.
# Для macOS/Linux
python3 -m venv venv
source venv/bin/activate
# Для Windows
python -m venv venv
venv\Scripts\activate
Вы увидите (venv) в командной строке, что означает, что окружение активно.
2. Установка зависимостей
Нам нужны две основные библиотеки: fastapi для самого фреймворка и uvicorn для запуска сервера.
pip install fastapi "uvicorn[standard]"
3. Создание API (main.py)
Создайте файл main.py внутри папки backend. Здесь мы определим наши модели данных, "базу данных" в памяти и эндпоинты API.
# backend/main.py
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import List
# --- Модели Pydantic для валидации данных ---
class TaskBase(BaseModel):
title: str
is_completed: bool = False
class TaskCreate(TaskBase):
pass
class Task(TaskBase):
id: int
# --- "База данных" в памяти ---
tasks_db = {
1: Task(id=1, title="Изучить FastAPI", is_completed=True),
2: Task(id=2, title="Создать React-приложение", is_completed=False),
3: Task(id=3, title="Стилизовать с Tailwind CSS", is_completed=False),
}
next_task_id = 4
# --- Инициализация приложения FastAPI ---
app = FastAPI(
title="Task Manager API",
description="Простое API для управления задачами.",
version="1.0.0",
)
# --- Middleware для CORS (Cross-Origin Resource Sharing) ---
# Это критически важно! Позволяет нашему React-приложению (на другом порту)
# отправлять запросы к нашему FastAPI-бэкенду.
origins = [
"http://localhost:5173",
"http://127.0.0.1:5173",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# --- Эндпоинты API ---
@app.get("/api/tasks", response_model=List[Task], tags=["Tasks"])
async def get_all_tasks():
"""Получить все задачи."""
return list(tasks_db.values())
@app.post("/api/tasks", response_model=Task, status_code=201, tags=["Tasks"])
async def create_new_task(task: TaskCreate):
"""Создать новую задачу."""
global next_task_id
new_task = Task(id=next_task_id, **task.dict())
tasks_db[next_task_id] = new_task
next_task_id += 1
return new_task
@app.delete("/api/tasks/{task_id}", status_code=204, tags=["Tasks"])
async def delete_existing_task(task_id: int):
"""Удалить задачу по ID."""
if task_id not in tasks_db:
raise HTTPException(status_code=404, detail="Задача не найдена")
del tasks_db[task_id]
return
@app.patch("/api/tasks/{task_id}", response_model=Task, tags=["Tasks"])
async def update_task_status(task_id: int, updates: dict):
"""Обновить статус выполнения задачи."""
if task_id not in tasks_db:
raise HTTPException(status_code=404, detail="Задача не найдена")
task = tasks_db[task_id]
if 'is_completed' in updates:
task.is_completed = updates['is_completed']
return task
4. Запуск бэкенда
В терминале (в директории backend) запустите сервер:
uvicorn main:app --reload
Откройте в браузере http://127.0.0.1:8000/docs. Вы увидите красивую интерактивную документацию Swagger UI, которую FastAPI сгенерировал для вас.
Часть 2: Создание фронтенда на React с Tailwind CSS
Теперь, когда наш API работает, займемся пользовательским интерфейсом.
1. Создание React-приложения с помощью Vite
Вернитесь в корневую папку проекта и используйте Vite для создания нового React-проекта в папке frontend.
cd .. # Возвращаемся в корень проекта
npm create vite@latest frontend -- --template react-ts
(Я использую шаблон react-ts для TypeScript, что настоятельно рекомендуется для современных React-проектов. )
Следуйте инструкциям, затем перейдите в новую директорию и установите зависимости.
cd frontend
npm install
2. Установка и настройка Tailwind CSS
Далее — стили. Мы будем следовать официальному руководству.
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Это создаст файлы tailwind.config.js и postcss.config.js. Настроим Tailwind для сканирования ваших файлов. Откройте tailwind.config.js и измените массив content:
// frontend/tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}", // Указываем Tailwind, где искать классы
],
theme: {
extend: {},
},
plugins: [],
}
Наконец, добавьте директивы Tailwind в ваш основной CSS-файл. Замените содержимое src/index.css этим:
/* frontend/src/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
3. Установка Axios
Нам нужна библиотека для выполнения HTTP-запросов. axios — это промышленный стандарт.
npm install axios
4. Создание UI (App.tsx)
Теперь самое интересное! Заменим содержимое src/App.tsx нашей логикой и JSX, стилизованным с помощью Tailwind.
// frontend/src/App.tsx
import React, { useState, useEffect, FormEvent } from 'react';
import axios from 'axios';
// --- Интерфейсы TypeScript ---
interface Task {
id: number;
title: string;
is_completed: boolean;
}
// --- Конфигурация API ---
const API_URL = 'http://127.0.0.1:8000/api';
function App( ) {
const [tasks, setTasks] = useState<Task[]>([]);
const [newTaskTitle, setNewTaskTitle] = useState("");
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// --- Загрузка задач с API ---
useEffect(() => {
const fetchTasks = async () => {
try {
const response = await axios.get(`${API_URL}/tasks`);
setTasks(response.data);
} catch (err) {
setError("Не удалось загрузить задачи. Бэкенд запущен?");
} finally {
setLoading(false);
}
};
fetchTasks();
}, []);
// --- Создание новой задачи ---
const handleCreateTask = async (e: FormEvent) => {
e.preventDefault();
if (!newTaskTitle.trim()) return;
try {
const response = await axios.post(`${API_URL}/tasks`, { title: newTaskTitle });
setTasks([...tasks, response.data]);
setNewTaskTitle("");
} catch (err) {
setError("Не удалось создать задачу.");
}
};
// --- Удаление задачи ---
const handleDeleteTask = async (taskId: number) => {
try {
await axios.delete(`${API_URL}/tasks/${taskId}`);
setTasks(tasks.filter(task => task.id !== taskId));
} catch (err) {
setError("Не удалось удалить задачу.");
}
};
// --- Переключение статуса задачи ---
const handleToggleTask = async (taskId: number, currentStatus: boolean) => {
try {
const response = await axios.patch(`${API_URL}/tasks/${taskId}`, { is_completed: !currentStatus });
setTasks(tasks.map(task => task.id === taskId ? response.data : task));
} catch (err) {
setError("Не удалось обновить задачу.");
}
};
return (
<div className="bg-slate-900 min-h-screen flex items-center justify-center font-sans">
<div className="w-full max-w-lg bg-slate-800 shadow-lg rounded-xl p-8">
<h1 className="text-4xl font-bold text-white text-center mb-8">Менеджер задач</h1>
{error && <p className="text-red-500 bg-red-900/50 p-3 rounded-md text-center mb-4">{error}</p>}
<form onSubmit={handleCreateTask} className="flex gap-4 mb-8">
<input
type="text"
value={newTaskTitle}
onChange={(e) => setNewTaskTitle(e.target.value)}
placeholder="Добавить новую задачу..."
className="flex-grow bg-slate-700 text-white placeholder-slate-400 rounded-md p-3 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition"
/>
<button
type="submit"
className="bg-indigo-600 text-white font-semibold px-6 py-3 rounded-md hover:bg-indigo-500 transition disabled:bg-slate-600"
disabled={!newTaskTitle.trim()}
>
Добавить
</button>
</form>
<div className="space-y-4">
{loading ? (
<p className="text-slate-400 text-center">Загрузка задач...</p>
) : (
tasks.map(task => (
<div
key={task.id}
className="bg-slate-700 p-4 rounded-lg flex items-center justify-between transition hover:bg-slate-600"
>
<div className="flex items-center gap-4">
<input
type="checkbox"
checked={task.is_completed}
onChange={() => handleToggleTask(task.id, task.is_completed)}
className="h-6 w-6 rounded bg-slate-600 border-slate-500 text-indigo-600 focus:ring-indigo-500"
/>
<span className={`text-lg ${task.is_completed ? 'line-through text-slate-500' : 'text-white'}`}>
{task.title}
</span>
</div>
<button
onClick={() => handleDeleteTask(task.id)}
className="text-slate-500 hover:text-red-500 transition"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</div>
))
)}
</div>
</div>
</div>
);
}
export default App;
5. Запуск фронтенда
В терминале (в папке frontend) запустите сервер разработки:
npm run dev
Ваше React-приложение теперь будет работать по адресу http://localhost:5173.
Часть 3: Гранд-финал
Убедитесь, что у вас запущены оба процесса в двух разных терминалах:
- Бэкенд FastAPI на http://127.0.0.1:8000
- Фронтенд React на http://localhost:5173
Теперь откройте в браузере http://localhost:5173.
Вы должны увидеть ваш красивый, полностью функциональный менеджер задач! Попробуйте добавить задачу, отметить ее как выполненную или удалить. Каждое действие бесшовно общается с вашим бэкендом на FastAPI, а интерфейс обновляется в реальном времени.
Заключение и следующие шаги
Поздравляю! Вы только что создали полноценное веб-приложение с использованием FastAPI, React и Tailwind CSS. Вы решили некоторые из самых распространенных проблем в современной веб-разработке, включая проектирование API, управление состоянием на фронтенде и междоменные запросы.
Этот проект — фантастическая основа. Отсюда вы можете:
- Добавить настоящую базу данных: Заменить список в памяти на PostgreSQL с помощью ORM, такой как SQLAlchemy.
- Реализовать аутентификацию пользователей: Использовать JWT-токены для создания списков задач для конкретных пользователей.
- Развернуть ваше приложение: Задеплоить бэкенд на Heroku или DigitalOcean, а фронтенд — на Vercel или Netlify.
Теперь у вас есть ключевые навыки для создания сложных, современных веб-приложений. Возможности безграничны.
Удачного кодинга!