mcpField
A
api-routes

api-routes

github.com/gigs-slc開発363 回インストール39 週間前に更新
リポジトリ

api-routes について

Expo 開発者向けに、API ルートの構築とデプロイ方法を案内するスキルです。リクエスト処理、サーバー側の秘密情報、EAS Hosting の実行環境の制約を扱います。

主な機能

  1. 01+api.ts によるルート定義
  2. 02HTTP メソッドと動的パスの処理
  3. 03パラメーター、ヘッダー、JSON の取得
  4. 04サーバー側の秘密情報と CORS の管理
  5. 05EAS Hosting へのデプロイ

活用シーン

  1. 01API キーをサーバー側に保持
  2. 02サードパーティ API のプロキシ
  3. 03データ検証と Webhook の受信
  4. 04サーバー側でのデータベース操作

インストール

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

$npx skills add https://github.com/gigs-slc/react-native-skills --skill api-routes

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

詳細

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 を使用してください
  • デバッグ用にサーバー側でエラーをログに記録してください

コメント