乐闻世界logo
搜索文章和话题

如何在 Astro 中创建和使用 API 路由?如何处理请求和响应?

2月21日 16:14

Astro 的 API 路由功能允许你在 Astro 项目中创建服务器端 API 端点,用于处理数据请求、身份验证、数据库操作等。

基本概念:

API 路由位于 src/pages/api/ 目录下,每个文件对应一个 API 端点。

创建 API 路由:

typescript
// src/pages/api/hello.ts export async function GET(context) { return new Response(JSON.stringify({ message: 'Hello, World!' }), { headers: { 'Content-Type': 'application/json', }, }); }

支持的 HTTP 方法:

typescript
// src/pages/api/users.ts export 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 }); }

动态路由参数:

typescript
// src/pages/api/users/[id].ts export 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' }, }); }

请求和响应处理:

typescript
// src/pages/api/search.ts export 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' }, }); } }

身份验证示例:

typescript
// src/pages/api/protected.ts export 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' }, }); }

文件上传:

typescript
// src/pages/api/upload.ts export 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' }, }); } }

使用数据库:

typescript
// src/pages/api/posts.ts import { 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' }, }); }

错误处理:

typescript
// src/lib/api-error.ts export 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' }, } ); }
typescript
// src/pages/api/data.ts import { 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 配置:

typescript
// src/pages/api/cors-example.ts export 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': '*', }, }); }

最佳实践:

  1. 使用适当的 HTTP 方法(GET、POST、PUT、DELETE)
  2. 实现适当的错误处理和状态码
  3. 验证和清理输入数据
  4. 使用 TypeScript 获得类型安全
  5. 实现身份验证和授权
  6. 添加适当的日志记录
  7. 使用环境变量管理敏感信息
  8. 实现速率限制防止滥用
  9. 使用适当的缓存策略
  10. 编写测试确保 API 可靠性

Astro 的 API 路由功能为构建全栈应用提供了强大的服务器端能力,同时保持了 Astro 的简洁和性能优势。

标签:Astro