api-routes
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWhen to Use API Routes
何时使用API路由
Use API routes when you need:
- Server-side secrets — API keys, database credentials, or tokens that must never reach the client
- Database operations — Direct database queries that shouldn't be exposed
- Third-party API proxies — Hide API keys when calling external services (OpenAI, Stripe, etc.)
- Server-side validation — Validate data before database writes
- Webhook endpoints — Receive callbacks from services like Stripe or GitHub
- Rate limiting — Control access at the server level
- Heavy computation — Offload processing that would be slow on mobile
当你需要以下功能时,使用API路由:
- 服务端密钥 —— 绝不能让客户端接触到的API密钥、数据库凭证或令牌
- 数据库操作 —— 不应暴露的直接数据库查询
- 第三方API代理 —— 调用外部服务(如OpenAI、Stripe等)时隐藏API密钥
- 服务端验证 —— 在写入数据库前验证数据
- Webhook端点 —— 接收来自Stripe或GitHub等服务的回调
- 速率限制 —— 在服务端控制访问
- 大量计算 —— 卸载在移动设备上运行缓慢的处理任务
When NOT to Use API Routes
何时不使用API路由
Avoid API routes when:
- Data is already public — Use direct fetch to public APIs instead
- No secrets required — Static data or client-safe operations
- Real-time updates needed — Use WebSockets or services like Supabase Realtime
- Simple CRUD — Consider Firebase, Supabase, or Convex for managed backends
- File uploads — Use direct-to-storage uploads (S3 presigned URLs, Cloudflare R2)
- Authentication only — Use Clerk, Auth0, or Firebase Auth instead
在以下场景避免使用API路由:
- 数据已公开 —— 直接调用公开API即可
- 无需密钥 —— 静态数据或客户端安全的操作
- 需要实时更新 —— 使用WebSockets或Supabase Realtime等服务
- 简单CRUD操作 —— 考虑使用Firebase、Supabase或Convex等托管后端
- 文件上传 —— 使用直接上传到存储的方式(如S3预签名URL、Cloudflare R2)
- 仅需身份验证 —— 使用Clerk、Auth0或Firebase Auth替代
File Structure
文件结构
API routes live in the directory with suffix:
app+api.tsapp/
api/
hello+api.ts → GET /api/hello
users+api.ts → /api/users
users/[id]+api.ts → /api/users/:id
(tabs)/
index.tsxAPI路由存放在目录下,以为后缀:
app+api.tsapp/
api/
hello+api.ts → GET /api/hello
users+api.ts → /api/users
users/[id]+api.ts → /api/users/:id
(tabs)/
index.tsxBasic API Route
基础API路由
ts
// app/api/hello+api.ts
export function GET(request: Request) {
return Response.json({ message: "Hello from Expo!" });
}ts
// app/api/hello+api.ts
export function GET(request: Request) {
return Response.json({ message: "Hello from Expo!" });
}HTTP Methods
HTTP方法
Export named functions for each HTTP method:
ts
// app/api/items+api.ts
export function GET(request: Request) {
return Response.json({ items: [] });
}
export async function POST(request: Request) {
const body = await request.json();
return Response.json({ created: body }, { status: 201 });
}
export async function PUT(request: Request) {
const body = await request.json();
return Response.json({ updated: body });
}
export async function DELETE(request: Request) {
return new Response(null, { status: 204 });
}为每个HTTP方法导出命名函数:
ts
// app/api/items+api.ts
export function GET(request: Request) {
return Response.json({ items: [] });
}
export async function POST(request: Request) {
const body = await request.json();
return Response.json({ created: body }, { status: 201 });
}
export async function PUT(request: Request) {
const body = await request.json();
return Response.json({ updated: body });
}
export async function DELETE(request: Request) {
return new Response(null, { status: 204 });
}Dynamic Routes
动态路由
ts
// app/api/users/[id]+api.ts
export function GET(request: Request, { id }: { id: string }) {
return Response.json({ userId: id });
}ts
// app/api/users/[id]+api.ts
export function GET(request: Request, { id }: { id: string }) {
return Response.json({ userId: id });
}Request Handling
请求处理
Query Parameters
查询参数
ts
export function GET(request: Request) {
const url = new URL(request.url);
const page = url.searchParams.get("page") ?? "1";
const limit = url.searchParams.get("limit") ?? "10";
return Response.json({ page, limit });
}ts
export function GET(request: Request) {
const url = new URL(request.url);
const page = url.searchParams.get("page") ?? "1";
const limit = url.searchParams.get("limit") ?? "10";
return Response.json({ page, limit });
}Headers
请求头
ts
export function GET(request: Request) {
const auth = request.headers.get("Authorization");
if (!auth) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
return Response.json({ authenticated: true });
}ts
export function GET(request: Request) {
const auth = request.headers.get("Authorization");
if (!auth) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
return Response.json({ authenticated: true });
}JSON Body
JSON请求体
ts
export async function POST(request: Request) {
const { email, password } = await request.json();
if (!email || !password) {
return Response.json({ error: "Missing fields" }, { status: 400 });
}
return Response.json({ success: true });
}ts
export async function POST(request: Request) {
const { email, password } = await request.json();
if (!email || !password) {
return Response.json({ error: "缺少必填字段" }, { status: 400 });
}
return Response.json({ success: true });
}Environment Variables
环境变量
Use for server-side secrets:
process.envts
// app/api/ai+api.ts
export async function POST(request: Request) {
const { prompt } = await request.json();
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: "gpt-4",
messages: [{ role: "user", content: prompt }],
}),
});
const data = await response.json();
return Response.json(data);
}Set environment variables:
- Local: Create file (never commit)
.env - EAS Hosting: Use or Expo dashboard
eas env:create
使用获取服务端密钥:
process.envts
// app/api/ai+api.ts
export async function POST(request: Request) {
const { prompt } = await request.json();
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: "gpt-4",
messages: [{ role: "user", content: prompt }],
}),
});
const data = await response.json();
return Response.json(data);
}设置环境变量:
- 本地开发:创建文件(切勿提交到版本库)
.env - EAS Hosting:使用命令或Expo控制台
eas env:create
CORS Headers
CORS请求头
Add CORS for web clients:
ts
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
};
export function OPTIONS() {
return new Response(null, { headers: corsHeaders });
}
export function GET() {
return Response.json({ data: "value" }, { headers: corsHeaders });
}为Web客户端添加CORS支持:
ts
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
};
export function OPTIONS() {
return new Response(null, { headers: corsHeaders });
}
export function GET() {
return Response.json({ data: "value" }, { headers: corsHeaders });
}Error Handling
错误处理
ts
export async function POST(request: Request) {
try {
const body = await request.json();
// Process...
return Response.json({ success: true });
} catch (error) {
console.error("API error:", error);
return Response.json({ error: "Internal server error" }, { status: 500 });
}
}ts
export async function POST(request: Request) {
try {
const body = await request.json();
// 处理逻辑...
return Response.json({ success: true });
} catch (error) {
console.error("API错误:", error);
return Response.json({ error: "内部服务器错误" }, { status: 500 });
}
}Testing Locally
本地测试
Start the development server with API routes:
bash
npx expo serveThis starts a local server at with full API route support.
http://localhost:8081Test with curl:
bash
curl http://localhost:8081/api/hello
curl -X POST http://localhost:8081/api/users -H "Content-Type: application/json" -d '{"name":"Test"}'启动包含API路由的开发服务器:
bash
npx expo serve该命令会在启动本地服务器,完全支持API路由。
http://localhost:8081使用curl测试:
bash
curl http://localhost:8081/api/hello
curl -X POST http://localhost:8081/api/users -H "Content-Type: application/json" -d '{"name":"Test"}'Deployment to EAS Hosting
部署到EAS Hosting
Prerequisites
前置条件
bash
npm install -g eas-cli
eas loginbash
npm install -g eas-cli
eas loginDeploy
部署命令
bash
eas deployThis builds and deploys your API routes to EAS Hosting (Cloudflare Workers).
bash
eas deploy该命令会将你的API路由构建并部署到EAS Hosting(基于Cloudflare Workers)。
Environment Variables for Production
生产环境环境变量
bash
undefinedbash
undefinedCreate a secret
创建密钥
eas env:create --name OPENAI_API_KEY --value sk-xxx --environment production
eas env:create --name OPENAI_API_KEY --value sk-xxx --environment production
Or use the Expo dashboard
或使用Expo控制台
undefinedundefinedCustom Domain
自定义域名
Configure in or Expo dashboard.
eas.json在或Expo控制台中配置。
eas.jsonEAS Hosting Runtime (Cloudflare Workers)
EAS Hosting运行时(Cloudflare Workers)
API routes run on Cloudflare Workers. Key limitations:
API路由运行在Cloudflare Workers上,以下是关键限制:
Missing/Limited APIs
缺失/受限的API
- No Node.js filesystem — module unavailable
fs - No native Node modules — Use Web APIs or polyfills
- Limited execution time — 30 second timeout for CPU-intensive tasks
- No persistent connections — WebSockets require Durable Objects
- fetch is available — Use standard fetch for HTTP requests
- 无Node.js文件系统 —— 模块不可用
fs - 无原生Node模块 —— 使用Web API或polyfill
- 有限的执行时间 —— CPU密集型任务超时时间为30秒
- 无持久连接 —— WebSocket需要使用Durable Objects
- fetch可用 —— 使用标准fetch发起HTTP请求
Use Web APIs Instead
使用Web API替代
ts
// Use Web Crypto instead of Node crypto
const hash = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode("data")
);
// Use fetch instead of node-fetch
const response = await fetch("https://api.example.com");
// Use Response/Request (already available)
return new Response(JSON.stringify(data), {
headers: { "Content-Type": "application/json" },
});ts
// 使用Web Crypto替代Node crypto
const hash = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode("data")
);
// 使用原生fetch替代node-fetch
const response = await fetch("https://api.example.com");
// 使用Response/Request(已内置)
return new Response(JSON.stringify(data), {
headers: { "Content-Type": "application/json" },
});Database Options
数据库选项
Since filesystem is unavailable, use cloud databases:
- Cloudflare D1 — SQLite at the edge
- Turso — Distributed SQLite
- PlanetScale — Serverless MySQL
- Supabase — Postgres with REST API
- Neon — Serverless Postgres
Example with Turso:
ts
// app/api/users+api.ts
import { createClient } from "@libsql/client/web";
const db = createClient({
url: process.env.TURSO_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
});
export async function GET() {
const result = await db.execute("SELECT * FROM users");
return Response.json(result.rows);
}由于文件系统不可用,请使用云数据库:
- Cloudflare D1 —— 边缘SQLite数据库
- Turso —— 分布式SQLite
- PlanetScale —— 无服务器MySQL
- Supabase —— 带REST API的Postgres
- Neon —— 无服务器Postgres
Turso使用示例:
ts
// app/api/users+api.ts
import { createClient } from "@libsql/client/web";
const db = createClient({
url: process.env.TURSO_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
});
export async function GET() {
const result = await db.execute("SELECT * FROM users");
return Response.json(result.rows);
}Calling API Routes from Client
从客户端调用API路由
ts
// From React Native components
const response = await fetch("/api/hello");
const data = await response.json();
// With body
const response = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "John" }),
});ts
// 在React Native组件中调用
const response = await fetch("/api/hello");
const data = await response.json();
// 带请求体的调用
const response = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "John" }),
});Common Patterns
常见模式
Authentication Middleware
身份验证中间件
ts
// utils/auth.ts
export async function requireAuth(request: Request) {
const token = request.headers.get("Authorization")?.replace("Bearer ", "");
if (!token) {
throw new Response(JSON.stringify({ error: "Unauthorized" }), {
status: 401,
headers: { "Content-Type": "application/json" },
});
}
// Verify token...
return { userId: "123" };
}
// app/api/protected+api.ts
import { requireAuth } from "../../utils/auth";
export async function GET(request: Request) {
const { userId } = await requireAuth(request);
return Response.json({ userId });
}ts
// utils/auth.ts
export async function requireAuth(request: Request) {
const token = request.headers.get("Authorization")?.replace("Bearer ", "");
if (!token) {
throw new Response(JSON.stringify({ error: "未授权" }), {
status: 401,
headers: { "Content-Type": "application/json" },
});
}
// 验证令牌...
return { userId: "123" };
}
// app/api/protected+api.ts
import { requireAuth } from "../../utils/auth";
export async function GET(request: Request) {
const { userId } = await requireAuth(request);
return Response.json({ userId });
}Proxy External API
代理外部API
ts
// app/api/weather+api.ts
export async function GET(request: Request) {
const url = new URL(request.url);
const city = url.searchParams.get("city");
const response = await fetch(
`https://api.weather.com/v1/current?city=${city}&key=${process.env.WEATHER_API_KEY}`
);
return Response.json(await response.json());
}ts
// app/api/weather+api.ts
export async function GET(request: Request) {
const url = new URL(request.url);
const city = url.searchParams.get("city");
const response = await fetch(
`https://api.weather.com/v1/current?city=${city}&key=${process.env.WEATHER_API_KEY}`
);
return Response.json(await response.json());
}Rules
规则
- NEVER expose API keys or secrets in client code
- ALWAYS validate and sanitize user input
- Use proper HTTP status codes (200, 201, 400, 401, 404, 500)
- Handle errors gracefully with try/catch
- Keep API routes focused — one responsibility per endpoint
- Use TypeScript for type safety
- Log errors server-side for debugging
- 切勿在客户端代码中暴露API密钥或机密信息
- 始终验证并清理用户输入
- 使用正确的HTTP状态码(200、201、400、401、404、500)
- 使用try/catch优雅处理错误
- 保持API路由职责单一——每个端点对应一个功能
- 使用TypeScript保证类型安全
- 在服务端记录错误以便调试