api-routes

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

When 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
app
directory with
+api.ts
suffix:
app/
  api/
    hello+api.ts          → GET /api/hello
    users+api.ts          → /api/users
    users/[id]+api.ts     → /api/users/:id
  (tabs)/
    index.tsx
API路由存放在
app
目录下,以
+api.ts
为后缀:
app/
  api/
    hello+api.ts          → GET /api/hello
    users+api.ts          → /api/users
    users/[id]+api.ts     → /api/users/:id
  (tabs)/
    index.tsx

Basic 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
process.env
for server-side secrets:
ts
// 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
    .env
    file (never commit)
  • EAS Hosting: Use
    eas env:create
    or Expo dashboard
使用
process.env
获取服务端密钥:
ts
// 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:使用
    eas env:create
    命令或Expo控制台

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 serve
This starts a local server at
http://localhost:8081
with full API route support.
Test 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
该命令会在
http://localhost:8081
启动本地服务器,完全支持API路由。
使用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 login
bash
npm install -g eas-cli
eas login

Deploy

部署命令

bash
eas deploy
This builds and deploys your API routes to EAS Hosting (Cloudflare Workers).
bash
eas deploy
该命令会将你的API路由构建并部署到EAS Hosting(基于Cloudflare Workers)。

Environment Variables for Production

生产环境环境变量

bash
undefined
bash
undefined

Create 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控制台

undefined
undefined

Custom Domain

自定义域名

Configure in
eas.json
or Expo dashboard.
eas.json
或Expo控制台中配置。

EAS 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
    fs
    module unavailable
  • 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保证类型安全
  • 在服务端记录错误以便调试