如何在 Astro 中创建和使用 API 路由?如何处理请求和响应?
Astro 的 API 路由功能允许你在 Astro 项目中创建服务器端 API 端点,用于处理数据请求、身份验证、数据库操作等。基本概念:API 路由位于 src/pages/api/ 目录下,每个文件对应一个 API 端点。创建 API 路由:// src/pages/api/hello.tsexport async function GET(context) { return new Response(JSON.stringify({ message: 'Hello, World!' }), { headers: { 'Content-Type': 'application/json', }, });}支持的 HTTP 方法:// src/pages/api/users.tsexport async function GET(context) { const users = await fetchUsers(); return new Response(JSON.stringify(users), { headers: { 'Content-Type': 'application/json' }, });}export async function POST(context) { const body = await context.request.json(); const newUser = await createUser(body); return new Response(JSON.stringify(newUser), { status: 201, headers: { 'Content-Type': 'application/json' }, });}export async function PUT(context) { const body = await context.request.json(); const updatedUser = await updateUser(body); return new Response(JSON.stringify(updatedUser), { headers: { 'Content-Type': 'application/json' }, });}export async function DELETE(context) { const { id } = context.params; await deleteUser(id); return new Response(null, { status: 204 });}动态路由参数:// src/pages/api/users/[id].tsexport async function GET(context) { const { id } = context.params; const user = await fetchUserById(id); if (!user) { return new Response(JSON.stringify({ error: 'User not found' }), { status: 404, headers: { 'Content-Type': 'application/json' }, }); } return new Response(JSON.stringify(user), { headers: { 'Content-Type': 'application/json' }, });}请求和响应处理:// src/pages/api/search.tsexport async function POST(context) { try { // 获取请求体 const body = await context.request.json(); const { query } = body; // 获取查询参数 const url = new URL(context.request.url); const limit = parseInt(url.searchParams.get('limit') || '10'); // 获取请求头 const authHeader = context.request.headers.get('Authorization'); // 获取 Cookie const sessionCookie = context.cookies.get('session'); // 处理业务逻辑 const results = await search(query, limit); // 返回响应 return new Response(JSON.stringify({ results }), { status: 200, headers: { 'Content-Type': 'application/json', 'Cache-Control': 'public, max-age=3600', }, }); } catch (error) { return new Response(JSON.stringify({ error: 'Internal Server Error' }), { status: 500, headers: { 'Content-Type': 'application/json' }, }); }}身份验证示例:// src/pages/api/protected.tsexport async function GET(context) { const token = context.request.headers.get('Authorization'); if (!token) { return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401, headers: { 'Content-Type': 'application/json' }, }); } const user = await verifyToken(token); if (!user) { return new Response(JSON.stringify({ error: 'Invalid token' }), { status: 403, headers: { 'Content-Type': 'application/json' }, }); } const data = await fetchProtectedData(user.id); return new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' }, });}文件上传:// src/pages/api/upload.tsexport async function POST(context) { try { const formData = await context.request.formData(); const file = formData.get('file') as File; if (!file) { return new Response(JSON.stringify({ error: 'No file provided' }), { status: 400, headers: { 'Content-Type': 'application/json' }, }); } // 验证文件类型 const allowedTypes = ['image/jpeg', 'image/png', 'image/gif']; if (!allowedTypes.includes(file.type)) { return new Response(JSON.stringify({ error: 'Invalid file type' }), { status: 400, headers: { 'Content-Type': 'application/json' }, }); } // 验证文件大小(最大 5MB) const maxSize = 5 * 1024 * 1024; if (file.size > maxSize) { return new Response(JSON.stringify({ error: 'File too large' }), { status: 400, headers: { 'Content-Type': 'application/json' }, }); } // 保存文件 const url = await uploadFile(file); return new Response(JSON.stringify({ url }), { status: 201, headers: { 'Content-Type': 'application/json' }, }); } catch (error) { return new Response(JSON.stringify({ error: 'Upload failed' }), { status: 500, headers: { 'Content-Type': 'application/json' }, }); }}使用数据库:// src/pages/api/posts.tsimport { db } from '../../lib/db';export async function GET(context) { const url = new URL(context.request.url); const page = parseInt(url.searchParams.get('page') || '1'); const limit = parseInt(url.searchParams.get('limit') || '10'); const offset = (page - 1) * limit; const posts = await db.post.findMany({ take: limit, skip: offset, orderBy: { createdAt: 'desc' }, }); const total = await db.post.count(); return new Response(JSON.stringify({ posts, pagination: { page, limit, total, totalPages: Math.ceil(total / limit), }, }), { headers: { 'Content-Type': 'application/json' }, });}export async function POST(context) { const body = await context.request.json(); const { title, content, authorId } = body; const post = await db.post.create({ data: { title, content, authorId, }, }); return new Response(JSON.stringify(post), { status: 201, headers: { 'Content-Type': 'application/json' }, });}错误处理:// src/lib/api-error.tsexport class ApiError extends Error { constructor( public statusCode: number, message: string, public code?: string ) { super(message); this.name = 'ApiError'; }}export function handleApiError(error: unknown) { if (error instanceof ApiError) { return new Response( JSON.stringify({ error: error.message, code: error.code, }), { status: error.statusCode, headers: { 'Content-Type': 'application/json' }, } ); } console.error('Unexpected error:', error); return new Response( JSON.stringify({ error: 'Internal Server Error' }), { status: 500, headers: { 'Content-Type': 'application/json' }, } );}// src/pages/api/data.tsimport { ApiError, handleApiError } from '../../lib/api-error';export async function GET(context) { try { const data = await fetchData(); if (!data) { throw new ApiError(404, 'Data not found', 'NOT_FOUND'); } return new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' }, }); } catch (error) { return handleApiError(error); }}CORS 配置:// src/pages/api/cors-example.tsexport async function OPTIONS(context) { return new Response(null, { status: 204, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', 'Access-Control-Max-Age': '86400', }, });}export async function GET(context) { const data = await fetchData(); return new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', }, });}最佳实践:使用适当的 HTTP 方法(GET、POST、PUT、DELETE)实现适当的错误处理和状态码验证和清理输入数据使用 TypeScript 获得类型安全实现身份验证和授权添加适当的日志记录使用环境变量管理敏感信息实现速率限制防止滥用使用适当的缓存策略编写测试确保 API 可靠性Astro 的 API 路由功能为构建全栈应用提供了强大的服务器端能力,同时保持了 Astro 的简洁和性能优势。