Litestar与前端框架集成:React/Vue调用API的最佳实践

Litestar与前端框架集成:React/Vue调用API的最佳实践

Litestar与前端框架集成:React/Vue调用API的最佳实践

【免费下载链接】litestar Production-ready, Light, Flexible and Extensible ASGI API framework | Effortlessly Build Performant APIs 项目地址: https://gitcode.***/GitHub_Trending/li/litestar

引言:解决前后端集成的核心痛点

在现代Web开发中,前端框架(如React和Vue)与后端API的无缝集成是项目成功的关键。然而,开发者常常面临跨域资源共享(CORS)配置复杂、认证流程繁琐、数据验证困难以及错误处理不一致等问题。Litestar作为一款高性能的ASGI框架,提供了简洁而强大的工具来解决这些挑战。本文将详细介绍如何在Litestar后端中实现最佳实践,以便React和Vue前端能够高效、安全地调用API。

读完本文后,你将能够:

  • 配置Litestar以支持React和Vue前端的跨域请求
  • 实现安全的JWT认证机制
  • 使用数据传输对象(DTO)验证前端发送的数据
  • 构建RESTful API端点并处理各种响应状态码
  • 在React和Vue中编写高效的API调用代码
  • 处理常见的集成问题和错误场景

1. Litestar后端配置:为前端集成奠定基础

1.1 CORS配置:消除跨域障碍

跨域资源共享(CORS)是前端调用API时最常见的障碍之一。Litestar提供了CORSConfig类来简化这一配置过程。

from litestar import Litestar
from litestar.config.cors import CORSConfig

cors_config = CORSConfig(
    allow_origins=["http://localhost:3000", "http://localhost:8080"],  # React和Vue开发服务器
    allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    allow_headers=["Content-Type", "Authorization"],
    allow_credentials=True,
    max_age=3600
)

app = Litestar(cors_config=cors_config, route_handlers=[...])

上述配置允许来自React(通常运行在3000端口)和Vue(通常运行在8080端口)开发服务器的请求,并支持常见的HTTP方法和必要的请求头。allow_credentials设置允许跨域请求携带cookie,这对于某些认证场景至关重要。

1.2 JWT认证:保护API安全

为了确保API的安全性,我们将使用JSON Web Token(JWT)认证。Litestar提供了完整的JWT支持。

from litestar import Litestar, get, post
from litestar.security.jwt import JWTAuth, Token

# 配置JWT
jwt_auth = JWTAuth(
    secret="your-secret-key-here",  # 在生产环境中使用环境变量
    algorithm="HS256",
    a***ess_token_expiration=3600,  # 1小时过期
)

# 登录端点 - 无需认证
@post("/login")
async def login(username: str, password: str) -> dict[str, str]:
    # 这里应该有实际的用户验证逻辑
    if username == "admin" and password == "password":  # 仅作示例,生产环境需使用密码哈希
        token = jwt_auth.encode({"sub": username, "role": "admin"})
        return {"a***ess_token": token}
    
    raise HTTPException(status_code=401, detail="Invalid credentials")

# 受保护的端点
@get("/protected", guards=[jwt_auth.guard])
async def protected_route(token: Token) -> dict[str, str]:
    return {"message": f"Hello, {token.sub}!", "role": token.role}

app = Litestar(
    route_handlers=[login, protected_route],
    middleware=[jwt_auth.middleware],
)

1.3 数据验证与DTO:确保数据完整性

Litestar的DTO(数据传输对象)功能可以轻松验证和转换前端发送的数据。

from litestar.dto import DataclassDTO, dto_field
from dataclasses import dataclass
from litestar import post, put, Litestar

@dataclass
class TodoItem:
    title: str
    description: str = dto_field(default="", min_length=0, max_length=500)
    ***pleted: bool = False

class TodoItemDTO(DataclassDTO[TodoItem]):
    config = DataclassDTO.Config(rename_strategy="camel_case")  # 支持前端常用的驼峰命名

@post("/todos", dto=TodoItemDTO, status_code=201)
async def create_todo(data: TodoItem) -> TodoItem:
    # 保存逻辑将在这里实现
    return data

@put("/todos/{todo_id:int}", dto=TodoItemDTO)
async def update_todo(todo_id: int, data: TodoItem) -> TodoItem:
    # 更新逻辑将在这里实现
    return data

app = Litestar(route_handlers=[create_todo, update_todo])

2. 构建RESTful API:Litestar后端实现

2.1 设计Todo API端点

我们将构建一个完整的Todo API,包含以下端点:

方法 端点 描述 认证 required
GET /todos 获取所有待办事项
GET /todos/{id} 获取单个待办事项
POST /todos 创建新的待办事项
PUT /todos/{id} 更新待办事项
DELETE /todos/{id} 删除待办事项
POST /login 用户登录

2.2 实现完整的后端代码

from litestar import Litestar, get, post, put, delete, HTTPException, status_codes
from litestar.config.cors import CORSConfig
from litestar.security.jwt import JWTAuth, Token
from litestar.dto import DataclassDTO, dto_field
from dataclasses import dataclass
from typing import List, Optional, Dict
from uuid import uuid4, UUID

# 数据模型
@dataclass
class TodoItem:
    id: UUID = dto_field(default_factory=uuid4, read_only=True)
    title: str = dto_field(min_length=1, max_length=100)
    description: str = dto_field(default="", min_length=0, max_length=500)
    ***pleted: bool = False

# DTO配置
class TodoItemDTO(DataclassDTO[TodoItem]):
    config = DataclassDTO.Config(rename_strategy="camel_case")

# 模拟数据库
TODO_DATABASE: Dict[UUID, TodoItem] = {}

# CORS配置
cors_config = CORSConfig(
    allow_origins=["http://localhost:3000", "http://localhost:8080"],
    allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    allow_headers=["Content-Type", "Authorization"],
    allow_credentials=True,
    max_age=3600
)

# JWT认证
jwt_auth = JWTAuth(
    secret="your-secret-key-here",  # 在生产环境中使用环境变量
    algorithm="HS256",
    a***ess_token_expiration=3600,
)

# 登录端点
@post("/login")
async def login(username: str, password: str) -> dict[str, str]:
    # 实际应用中应该验证用户凭据
    if username == "admin" and password == "password":  # 仅作示例
        token = jwt_auth.encode({"sub": username, "role": "admin"})
        return {"a***ess_token": token}
    
    raise HTTPException(status_code=401, detail="Invalid credentials")

# Todo API端点
@get("/todos", guards=[jwt_auth.guard])
async def get_todos() -> List[TodoItem]:
    return list(TODO_DATABASE.values())

@get("/todos/{todo_id:uuid}", guards=[jwt_auth.guard])
async def get_todo(todo_id: UUID) -> TodoItem:
    todo = TODO_DATABASE.get(todo_id)
    if not todo:
        raise HTTPException(status_code=404, detail="Todo not found")
    return todo

@post("/todos", dto=TodoItemDTO, status_code=201, guards=[jwt_auth.guard])
async def create_todo(data: TodoItem) -> TodoItem:
    TODO_DATABASE[data.id] = data
    return data

@put("/todos/{todo_id:uuid}", dto=TodoItemDTO, guards=[jwt_auth.guard])
async def update_todo(todo_id: UUID, data: TodoItem) -> TodoItem:
    if todo_id not in TODO_DATABASE:
        raise HTTPException(status_code=404, detail="Todo not found")
    
    updated_todo = TodoItem(id=todo_id, **data.__dict__)
    TODO_DATABASE[todo_id] = updated_todo
    return updated_todo

@delete("/todos/{todo_id:uuid}", status_code=204, guards=[jwt_auth.guard])
async def delete_todo(todo_id: UUID) -> None:
    if todo_id not in TODO_DATABASE:
        raise HTTPException(status_code=404, detail="Todo not found")
    
    del TODO_DATABASE[todo_id]

# 创建应用实例
app = Litestar(
    route_handlers=[login, get_todos, get_todo, create_todo, update_todo, delete_todo],
    middleware=[jwt_auth.middleware],
    cors_config=cors_config,
)

3. React前端集成

3.1 设置API服务

首先,创建一个API服务文件来处理与后端的通信:

// src/services/api.js
const API_BASE_URL = 'http://localhost:8000';

// 创建请求头
const createHeaders = (includeAuth = true) => {
  const headers = {
    'Content-Type': 'application/json',
  };
  
  if (includeAuth) {
    const token = localStorage.getItem('a***ess_token');
    if (token) {
      headers['Authorization'] = `Bearer ${token}`;
    }
  }
  
  return headers;
};

// API调用函数
export const api = {
  login: async (credentials) => {
    const response = await fetch(`${API_BASE_URL}/login`, {
      method: 'POST',
      headers: createHeaders(false),
      body: JSON.stringify(credentials),
    });
    
    if (!response.ok) {
      throw new Error('Authentication failed');
    }
    
    const data = await response.json();
    localStorage.setItem('a***ess_token', data.a***ess_token);
    return data;
  },
  
  getTodos: async () => {
    const response = await fetch(`${API_BASE_URL}/todos`, {
      headers: createHeaders(),
    });
    
    if (!response.ok) {
      throw new Error('Failed to fetch todos');
    }
    
    return response.json();
  },
  
  getTodo: async (id) => {
    const response = await fetch(`${API_BASE_URL}/todos/${id}`, {
      headers: createHeaders(),
    });
    
    if (!response.ok) {
      throw new Error('Failed to fetch todo');
    }
    
    return response.json();
  },
  
  createTodo: async (todoData) => {
    const response = await fetch(`${API_BASE_URL}/todos`, {
      method: 'POST',
      headers: createHeaders(),
      body: JSON.stringify(todoData),
    });
    
    if (!response.ok) {
      throw new Error('Failed to create todo');
    }
    
    return response.json();
  },
  
  updateTodo: async (id, todoData) => {
    const response = await fetch(`${API_BASE_URL}/todos/${id}`, {
      method: 'PUT',
      headers: createHeaders(),
      body: JSON.stringify(todoData),
    });
    
    if (!response.ok) {
      throw new Error('Failed to update todo');
    }
    
    return response.json();
  },
  
  deleteTodo: async (id) => {
    const response = await fetch(`${API_BASE_URL}/todos/${id}`, {
      method: 'DELETE',
      headers: createHeaders(),
    });
    
    if (!response.ok) {
      throw new Error('Failed to delete todo');
    }
    
    return response.status === 204;
  },
  
  logout: () => {
    localStorage.removeItem('a***ess_token');
  }
};

3.2 创建React组件

3.2.1 登录组件
// src/***ponents/Login.js
import React, { useState } from 'react';
import { api } from '../services/api';
import { useNavigate } from 'react-router-dom';

const Login = () => {
  const [credentials, setCredentials] = useState({ username: '', password: '' });
  const [error, setError] = useState('');
  const navigate = useNavigate();
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setCredentials(prev => ({ ...prev, [name]: value }));
  };
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    setError('');
    
    try {
      await api.login(credentials);
      navigate('/todos');
    } catch (err) {
      setError(err.message);
    }
  };
  
  return (
    <div className="login-container">
      <h2>Login</h2>
      {error && <div className="error-message">{error}</div>}
      <form onSubmit={handleSubmit}>
        <div className="form-group">
          <label>Username:</label>
          <input
            type="text"
            name="username"
            value={credentials.username}
            onChange={handleChange}
            required
          />
        </div>
        <div className="form-group">
          <label>Password:</label>
          <input
            type="password"
            name="password"
            value={credentials.password}
            onChange={handleChange}
            required
          />
        </div>
        <button type="submit">Login</button>
      </form>
    </div>
  );
};

export default Login;
3.2.2 Todo列表组件
// src/***ponents/TodoList.js
import React, { useState, useEffect } from 'react';
import { api } from '../services/api';
import { Link } from 'react-router-dom';

const TodoList = () => {
  const [todos, setTodos] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');
  
  useEffect(() => {
    const fetchTodos = async () => {
      try {
        const data = await api.getTodos();
        setTodos(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };
    
    fetchTodos();
  }, []);
  
  const handleDelete = async (id) => {
    if (window.confirm('Are you sure you want to delete this todo?')) {
      try {
        await api.deleteTodo(id);
        setTodos(todos.filter(todo => todo.id !== id));
      } catch (err) {
        setError(err.message);
      }
    }
  };
  
  if (loading) return <div>Loading todos...</div>;
  if (error) return <div className="error-message">Error: {error}</div>;
  
  return (
    <div className="todo-list">
      <div className="header">
        <h2>Todo List</h2>
        <Link to="/todos/new" className="btn-add">Add New Todo</Link>
      </div>
      
      {todos.length === 0 ? (
        <p>No todos found. Create your first todo!</p>
      ) : (
        <table>
          <thead>
            <tr>
              <th>Title</th>
              <th>Description</th>
              <th>***pleted</th>
              <th>Actions</th>
            </tr>
          </thead>
          <tbody>
            {todos.map(todo => (
              <tr key={todo.id} className={todo.***pleted ? '***pleted' : ''}>
                <td>{todo.title}</td>
                <td>{todo.description}</td>
                <td>{todo.***pleted ? 'Yes' : 'No'}</td>
                <td className="actions">
                  <Link to={`/todos/${todo.id}`} className="btn-view">View</Link>
                  <Link to={`/todos/${todo.id}/edit`} className="btn-edit">Edit</Link>
                  <button 
                    className="btn-delete" 
                    onClick={() => handleDelete(todo.id)}
                  >
                    Delete
                  </button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
    </div>
  );
};

export default TodoList;

4. Vue前端集成

4.1 设置API服务

// src/services/api.js
import axios from 'axios';

const API_BASE_URL = 'http://localhost:8000';

const apiClient = axios.create({
  baseURL: API_BASE_URL,
  headers: {
    'Content-Type': 'application/json',
  },
});

// 请求拦截器添加认证令牌
apiClient.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('a***ess_token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => Promise.reject(error)
);

// 响应拦截器处理常见错误
apiClient.interceptors.response.use(
  (response) => response,
  (error) => {
    // 处理401未授权错误
    if (error.response && error.response.status === 401) {
      localStorage.removeItem('a***ess_token');
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

export const api = {
  login: async (credentials) => {
    const response = await apiClient.post('/login', credentials);
    localStorage.setItem('a***ess_token', response.data.a***ess_token);
    return response.data;
  },
  
  getTodos: async () => {
    const response = await apiClient.get('/todos');
    return response.data;
  },
  
  getTodo: async (id) => {
    const response = await apiClient.get(`/todos/${id}`);
    return response.data;
  },
  
  createTodo: async (todoData) => {
    const response = await apiClient.post('/todos', todoData);
    return response.data;
  },
  
  updateTodo: async (id, todoData) => {
    const response = await apiClient.put(`/todos/${id}`, todoData);
    return response.data;
  },
  
  deleteTodo: async (id) => {
    return await apiClient.delete(`/todos/${id}`);
  },
  
  logout: () => {
    localStorage.removeItem('a***ess_token');
  }
};

4.2 创建Vue组件

4.2.1 登录组件
<!-- src/***ponents/Login.vue -->
<template>
  <div class="login-container">
    <h2>Login</h2>
    <div v-if="error" class="error-message">{{ error }}</div>
    
    <form @submit.prevent="handleSubmit">
      <div class="form-group">
        <label for="username">Username:</label>
        <input
          type="text"
          id="username"
          v-model="credentials.username"
          required
        />
      </div>
      
      <div class="form-group">
        <label for="password">Password:</label>
        <input
          type="password"
          id="password"
          v-model="credentials.password"
          required
        />
      </div>
      
      <button type="submit" :disabled="loading">
        <span v-if="loading">Logging in...</span>
        <span v-else>Login</span>
      </button>
    </form>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { api } from '../services/api';

const credentials = ref({ username: '', password: '' });
const error = ref('');
const loading = ref(false);
const router = useRouter();

const handleSubmit = async () => {
  error.value = '';
  loading.value = true;
  
  try {
    await api.login(credentials.value);
    router.push('/todos');
  } catch (err) {
    error.value = err.response?.data?.detail || 'Authentication failed';
  } finally {
    loading.value = false;
  }
};
</script>

<style scoped>
.login-container {
  max-width: 400px;
  margin: 0 auto;
  padding: 20px;
  border: 1px solid #***c;
  border-radius: 5px;
}

.form-group {
  margin-bottom: 15px;
}

label {
  display: block;
  margin-bottom: 5px;
}

input {
  width: 100%;
  padding: 8px;
  box-sizing: border-box;
}

button {
  background-color: #4CAF50;
  color: white;
  padding: 10px 15px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:disabled {
  background-color: #*********;
  cursor: not-allowed;
}

.error-message {
  color: red;
  margin-bottom: 15px;
  padding: 10px;
  border: 1px solid #ff******;
  background-color: #ffe6e6;
}
</style>
4.2.2 Todo列表组件
<!-- src/***ponents/TodoList.vue -->
<template>
  <div class="todo-list">
    <div class="header">
      <h2>Todo List</h2>
      <router-link to="/todos/new" class="btn-add">Add New Todo</router-link>
    </div>
    
    <div v-if="loading" class="loading">Loading todos...</div>
    <div v-if="error" class="error-message">{{ error }}</div>
    
    <template v-else>
      <table v-if="todos.length > 0">
        <thead>
          <tr>
            <th>Title</th>
            <th>Description</th>
            <th>***pleted</th>
            <th>Actions</th>
          </tr>
        </thead>
        <tbody>
          <tr 
            v-for="todo in todos" 
            :key="todo.id" 
            :class="{ ***pleted: todo.***pleted }"
          >
            <td>{{ todo.title }}</td>
            <td>{{ todo.description }}</td>
            <td>{{ todo.***pleted ? 'Yes' : 'No' }}</td>
            <td class="actions">
              <router-link :to="`/todos/${todo.id}`" class="btn-view">View</router-link>
              <router-link :to="`/todos/${todo.id}/edit`" class="btn-edit">Edit</router-link>
              <button 
                class="btn-delete" 
                @click="handleDelete(todo.id)"
              >
                Delete
              </button>
            </td>
          </tr>
        </tbody>
      </table>
      
      <p v-else>No todos found. Create your first todo!</p>
    </template>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import { api } from '../services/api';

const todos = ref([]);
const loading = ref(true);
const error = ref('');

const fetchTodos = async () => {
  try {
    todos.value = await api.getTodos();
  } catch (err) {
    error.value = err.response?.data?.detail || 'Failed to fetch todos';
  } finally {
    loading.value = false;
  }
};

const handleDelete = async (id) => {
  if (confirm('Are you sure you want to delete this todo?')) {
    try {
      await api.deleteTodo(id);
      todos.value = todos.value.filter(todo => todo.id !== id);
    } catch (err) {
      error.value = err.response?.data?.detail || 'Failed to delete todo';
    }
  }
};

onMounted(fetchTodos);
</script>

<style scoped>
.todo-list {
  max-width: 1000px;
  margin: 0 auto;
  padding: 20px;
}

.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}

.loading {
  text-align: center;
  padding: 20px;
}

.error-message {
  color: red;
  padding: 10px;
  border: 1px solid #ff******;
  background-color: #ffe6e6;
  border-radius: 4px;
}

table {
  width: 100%;
  border-collapse: collapse;
}

th, td {
  padding: 10px;
  text-align: left;
  border-bottom: 1px solid #ddd;
}

th {
  background-color: #f2f2f2;
}

tr.***pleted {
  background-color: #e6ffe6;
}

.actions {
  display: flex;
  gap: 5px;
}

button, .btn-add, .btn-view, .btn-edit, .btn-delete {
  padding: 5px 10px;
  border-radius: 4px;
  text-decoration: none;
  font-size: 0.9em;
}

.btn-add {
  background-color: #4CAF50;
  color: white;
  border: none;
}

.btn-view {
  background-color: #2196F3;
  color: white;
}

.btn-edit {
  background-color: #ff9800;
  color: white;
}

.btn-delete {
  background-color: #f44336;
  color: white;
  border: none;
  cursor: pointer;
}
</style>

5. 高级集成技巧与最佳实践

5.1 状态管理

对于复杂应用,建议使用状态管理库来处理API数据:

React - 使用Redux Toolkit:

// src/features/todos/todoSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { api } from '../../services/api';

export const fetchTodos = createAsyncThunk(
  'todos/fetchTodos',
  async (_, { rejectWithValue }) => {
    try {
      return await api.getTodos();
    } catch (err) {
      return rejectWithValue(err.message);
    }
  }
);

const todoSlice = createSlice({
  name: 'todos',
  initialState: {
    items: [],
    loading: false,
    error: null,
  },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchTodos.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchTodos.fulfilled, (state, action) => {
        state.loading = false;
        state.items = action.payload;
      })
      .addCase(fetchTodos.rejected, (state, action) => {
        state.loading = false;
        state.error = action.payload;
      });
  },
});

export default todoSlice.reducer;

Vue - 使用Pinia:

// src/stores/todoStore.js
import { defineStore } from 'pinia';
import { api } from '../services/api';

export const useTodoStore = defineStore('todo', {
  state: () => ({
    items: [],
    loading: false,
    error: null,
  }),
  actions: {
    async fetchTodos() {
      this.loading = true;
      this.error = null;
      
      try {
        this.items = await api.getTodos();
      } catch (err) {
        this.error = err.response?.data?.detail || 'Failed to fetch todos';
      } finally {
        this.loading = false;
      }
    },
    
    async deleteTodo(id) {
      try {
        await api.deleteTodo(id);
        this.items = this.items.filter(todo => todo.id !== id);
      } catch (err) {
        this.error = err.response?.data?.detail || 'Failed to delete todo';
      }
    }
  },
});

5.2 API错误处理最佳实践

  1. 统一错误处理:创建全局错误处理机制
  2. 用户友好消息:将技术错误转换为用户可理解的消息
  3. 错误日志记录:在开发环境中记录详细错误信息
  4. 重试机制:对临时错误实现自动重试
  5. 表单验证:在提交前进行客户端验证

5.3 性能优化

  1. 请求缓存:缓存频繁访问的数据
  2. 分页加载:对于大量数据实现分页
  3. 懒加载:仅加载当前需要的数据
  4. 取消请求:在组件卸载时取消未完成的请求
  5. 压缩响应:启用Gzip/Brotli压缩

React中的取消请求示例:

import { useEffect, useState } from 'react';
import { api } from '../services/api';

const TodoDetail = ({ todoId }) => {
  const [todo, setTodo] = useState(null);
  
  useEffect(() => {
    const controller = new AbortController();
    
    const fetchTodo = async () => {
      try {
        const data = await api.getTodo(todoId, { signal: controller.signal });
        setTodo(data);
      } catch (err) {
        if (err.name !== 'AbortError') {
          console.error('Failed to fetch todo:', err);
        }
      }
    };
    
    fetchTodo();
    
    return () => {
      controller.abort(); // 组件卸载时取消请求
    };
  }, [todoId]);
  
  // 组件渲染...
};

6. 部署与生产环境配置

6.1 Litestar生产配置

# production.py
import os
from litestar import Litestar
from litestar.config.cors import CORSConfig
from litestar.security.jwt import JWTAuth

# 从环境变量获取配置
SECRET_KEY = os.environ.get("SECRET_KEY")
ALLOWED_ORIGINS = os.environ.get("ALLOWED_ORIGINS", "https://your-frontend-domain.***").split(",")

# 配置CORS
cors_config = CORSConfig(
    allow_origins=ALLOWED_ORIGINS,
    allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    allow_headers=["Content-Type", "Authorization"],
    allow_credentials=True,
    max_age=3600
)

# 配置JWT
jwt_auth = JWTAuth(
    secret=SECRET_KEY,
    algorithm="HS256",
    a***ess_token_expiration=3600,
)

# 导入路由和中间件
from app.routes import route_handlers
from app.middleware import middleware

# 创建应用
app = Litestar(
    route_handlers=route_handlers,
    middleware=[jwt_auth.middleware, *middleware],
    cors_config=cors_config,
    debug=False,  # 生产环境禁用调试模式
)

6.2 前端生产构建

React:

# 创建优化的生产构建
npm run build

# 或使用yarn
yarn build

Vue:

# 创建优化的生产构建
npm run build

# 或使用yarn
yarn build

7. 常见问题与解决方案

7.1 CORS问题

问题: 前端控制台出现CORS错误。

解决方案:

  • 确保Litestar的CORSConfig正确配置了allow_origins
  • 检查allow_methods是否包含所需的HTTP方法
  • 验证allow_headers是否包含自定义请求头

7.2 认证问题

问题: 即使登录成功,API请求仍返回401未授权。

解决方案:

  • 检查令牌是否正确存储和发送
  • 验证JWT密钥是否匹配
  • 检查令牌是否过期
  • 确保受保护路由正确应用了认证守卫

7.3 数据验证问题

问题: 前端发送的数据被后端拒绝。

解决方案:

  • 检查DTO定义是否与前端发送的数据结构匹配
  • 验证所有必填字段都已提供
  • 检查字段长度和格式限制
  • 在前端实现表单验证,与后端DTO规则匹配

8. 总结与展望

Litestar提供了强大而灵活的工具集,使React和Vue前端能够轻松、安全地与其后端API集成。通过正确配置CORS、实现JWT认证、使用DTO验证数据以及遵循最佳实践,开发者可以构建高性能、安全可靠的全栈应用。

未来,随着Litestar的不断发展,我们可以期待更多简化前后端集成的功能。同时,前端框架的持续演进也将提供更多优化API调用的方式。建议开发者保持关注这两个领域的最新发展,不断优化自己的集成策略。

记住,良好的前后端集成不仅关乎技术实现,还关乎开发体验和最终用户体验。通过本文介绍的最佳实践,你可以构建出既易于维护又能提供出色用户体验的应用程序。

9. 扩展学习资源

  • Litestar官方文档: https://litestar.dev/
  • React官方文档: https://reactjs.org/docs/getting-started.html
  • Vue官方文档: https://vuejs.org/guide/introduction.html
  • MDN Web API文档: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
  • JWT.io: https://jwt.io/introduction/
  • RESTful API设计最佳实践: https://restfulapi.***/

【免费下载链接】litestar Production-ready, Light, Flexible and Extensible ASGI API framework | Effortlessly Build Performant APIs 项目地址: https://gitcode.***/GitHub_Trending/li/litestar

转载请说明出处内容投诉
CSS教程网 » Litestar与前端框架集成:React/Vue调用API的最佳实践

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买