mcpField
E
expo-api-routes

expo-api-routes

github.com/expo開発3.5k37.2k 回インストール今日更新
リポジトリ

expo-api-routes について

Expo開発者向けのAPIルートスキルで、appディレクトリでのサーバーサイドエンドポイント作成とEAS Hostingへのデプロイを扱います。シークレットの保護、外部APIのプロキシ、Webhook処理などに役立ちます。

主な機能

  1. 01GET、POSTなどのHTTPルート作成
  2. 02動的ルート、ヘッダー、JSONの処理
  3. 03サーバー側環境変数でシークレットを管理
  4. 04CORSとエラー処理の設定
  5. 05EAS Hostingへのデプロイ

活用シーン

  1. 01外部APIキーの秘匿
  2. 02サービスからのWebhook受信
  3. 03データベース書き込み前のデータ検証
  4. 04負荷の高い処理のサーバー移行

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/expo/skills --skill expo-api-routes

インストール後、Claude Code で/expo-api-routesを入力して読み込まれたか確認します。

同梱ファイルagents/openai.yaml

詳細

API ルートを使用するタイミング

次のような場合は、API ルートを使用してください。

  • サーバー側のシークレット — API キー、データベースの認証情報、クライアントに決して渡してはならないトークン
  • データベース操作 — 外部に公開すべきでないデータベースへの直接クエリ
  • サードパーティ API のプロキシ — 外部サービス(OpenAI、Stripe など)を呼び出す際に API キーを隠す
  • サーバー側の検証 — データベースへの書き込み前にデータを検証する
  • Webhook エンドポイント — Stripe や GitHub などのサービスからコールバックを受け取る
  • レート制限 — サーバー側でアクセスを制御する
  • 負荷の高い計算処理 — モバイルで時間がかかる処理をオフロードする

API ルートを使用しないタイミング

次のような場合は、API ルートを避けてください。

  • データがすでに公開されている — 代わりに公開 API を直接 fetch する
  • シークレットが不要 — 静的データやクライアントで安全に実行できる操作
  • リアルタイムの更新が必要 — WebSockets や Supabase Realtime などのサービスを使用する
  • シンプルな CRUD — マネージドバックエンドとして Firebase、Supabase、Convex の利用を検討する
  • ファイルのアップロード — ストレージへの直接アップロード(S3 の presigned URL、Cloudflare R2)を使用する
  • 認証のみ — 代わりに Clerk、Auth0、Firebase Auth を使用する

ファイル構成

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

基本的な API ルート

// app/api/hello+api.ts
export function GET(request: Request) {
  return Response.json({ message: "Hello from Expo!" });
}

HTTP メソッド

HTTP メソッドごとに名前付き関数をエクスポートします。

// 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 });
}

動的ルート

// app/api/users/[id]+api.ts
export function GET(request: Request, { id }: { id: string }) {
  return Response.json({ userId: id });
}

リクエストの処理

クエリパラメーター
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 });
}
ヘッダー
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 ボディ
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 });
}

環境変数

サーバー側のシークレットには process.env を使用します。

// 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 ヘッダー

Web クライアント用に CORS を追加します。

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 });
}

エラー処理

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 });
  }
}

ローカルでのテスト

API ルートを使用して開発サーバーを起動します。

npx expo serve

これにより、API ルートを完全にサポートするローカルサーバーが http://localhost:8081 で起動します。

curl でテストします。

curl http://localhost:8081/api/hello
curl -X POST http://localhost:8081/api/users -H "Content-Type: application/json" -d '{"name":"Test"}'

EAS Hosting へのデプロイ

前提条件
npm install -g eas-cli
eas login
デプロイ
eas deploy

これにより、API ルートがビルドされ、EAS Hosting(Cloudflare Workers)にデプロイされます。

本番環境用の環境変数
# Create a secret
eas env:create --name OPENAI_API_KEY --value sk-xxx --environment production

# Or use the Expo dashboard
カスタムドメイン

eas.json または Expo ダッシュボードで設定します。

EAS Hosting ランタイム(Cloudflare Workers)

API ルートは Cloudflare Workers 上で実行されます。主な制限事項は次のとおりです。

利用できない API/制限付き API
  • Node.js のファイルシステムは使用不可 — fs モジュールは利用できません
  • ネイティブの Node モジュールは使用不可 — Web API またはポリフィルを使用してください
  • 実行時間に制限あり — CPU 負荷の高いタスクはタイムアウトが 30 秒です
  • 永続的な接続は不可 — WebSockets には Durable Objects が必要です
  • fetch は利用可能 — HTTP リクエストには標準の fetch を使用してください
代わりに Web API を使用
// 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" },
});
データベースの選択肢

ファイルシステムは利用できないため、クラウドデータベースを使用してください。

  • Cloudflare D1 — エッジで動作する SQLite
  • Turso — 分散型 SQLite
  • PlanetScale — サーバーレス MySQL
  • Supabase — REST API を備えた Postgres
  • Neon — サーバーレス Postgres

Turso を使用する例:

// 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);
}

クライアントから API ルートを呼び出す

// 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" }),
});

よく使われるパターン

認証ミドルウェア
// 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 });
}
外部 API のプロキシ
// 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());
}

ルール

  • API キーやシークレットをクライアントコードに決して公開しないでください
  • ユーザー入力は必ず検証してサニタイズしてください
  • 適切な HTTP ステータスコード(200、201、400、401、404、500)を使用してください
  • try/catch を使ってエラーを適切に処理してください
  • API ルートの役割を絞り、エンドポイントごとに責任を 1 つにしてください
  • 型安全性のために TypeScript を使用してください
  • デバッグ用にサーバー側でエラーをログに記録してください

コメント