SvelteKitのAPIルートにCORS対応を追加した話
SvelteKitの +server.ts で作ったAPIを別オリジンから呼び出すには、CORSヘッダーの設定が必要です。
CORSの仕組み
ブラウザには同一オリジンポリシーがあり、異なるオリジン(ドメイン・ポート)へのリクエストはデフォルトでブロックされます。CORSはサーバー側がヘッダーで「このオリジンからのアクセスを許可する」と伝える仕組みです。
シンプルリクエストとプリフライトリクエスト
GET などの単純なリクエストはそのまま送られますが、Content-Type: application/json の POST や PUT / DELETE などの条件を超えたリクエストは、本番リクエストの前に OPTIONS リクエスト(プリフライト)を自動で送ります。
① ブラウザ → OPTIONS /api/posts (「このリクエストを送っていい?」)
② サーバー → 204 + CORSヘッダー (「いいよ」)
③ ブラウザ → POST /api/posts (本来のリクエスト)
④ サーバー → 201 + CORSヘッダー (レスポンス)
実装:+server.ts に個別に設定する
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
const CORS_HEADERS = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
};
// プリフライトリクエストへの対応
export const OPTIONS: RequestHandler = () => {
return new Response(null, { status: 204, headers: CORS_HEADERS });
};
export const GET: RequestHandler = async ({ platform }) => {
const result = await platform!.env.DB
.prepare('SELECT id, title FROM posts')
.all();
return json(result.results, { headers: CORS_HEADERS });
};
export const POST: RequestHandler = async ({ request, platform }) => {
const body = await request.json();
await platform!.env.DB
.prepare('INSERT INTO posts (title, body) VALUES (?, ?)')
.bind(body.title, body.body)
.run();
return json({ success: true }, { status: 201, headers: CORS_HEADERS });
};
OPTIONS ハンドラを追加してプリフライトに応答するのがポイントです。
各ヘッダーの意味
| ヘッダー | 役割 |
|---|---|
Access-Control-Allow-Origin |
許可するオリジン(* または特定URL) |
Access-Control-Allow-Methods |
許可するHTTPメソッド |
Access-Control-Allow-Headers |
許可するリクエストヘッダー |
Access-Control-Allow-Credentials |
Cookie・認証情報の送信を許可するか |
* ではなく特定オリジンを指定する場合
Access-Control-Allow-Origin: * はすべてのオリジンを許可しますが、CookieやAuthorizationヘッダーは送れません。認証が必要なAPIでは特定のオリジンを指定します。
const CORS_HEADERS = {
'Access-Control-Allow-Origin': 'https://frontend.example.com',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Allow-Credentials': 'true', // Cookieを送る場合
};
複数オリジンを許可する
Access-Control-Allow-Origin には1つのオリジンしか指定できないため、複数許可したい場合はリクエストの Origin ヘッダーを見て動的に返します。
const ALLOWED_ORIGINS = [
'https://frontend.example.com',
'http://localhost:5173',
];
export const GET: RequestHandler = async ({ request, platform }) => {
const origin = request.headers.get('Origin') ?? '';
const allowedOrigin = ALLOWED_ORIGINS.includes(origin) ? origin : '';
const result = await platform!.env.DB
.prepare('SELECT id, title FROM posts')
.all();
return json(result.results, {
headers: {
'Access-Control-Allow-Origin': allowedOrigin,
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
};
hooks.server.ts で全体に適用する方法
すべてのAPIルートにまとめてCORSを適用したい場合は hooks.server.ts の handle で設定します。
const cors: Handle = async ({ event, resolve }) => {
if (event.request.method === 'OPTIONS') {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
});
}
const response = await resolve(event);
response.headers.set('Access-Control-Allow-Origin', '*');
return response;
};
export const handle = sequence(cors, auth, logger);
まとめ
- 別オリジンからAPIを呼ぶにはサーバー側でCORSヘッダーが必要
Content-Type: application/jsonのPOSTなどはプリフライト(OPTIONS)が先に飛ぶので、OPTIONSハンドラも追加する*を指定するとCookie・認証情報は送れないため、認証が必要なAPIは特定オリジンを指定する- 全ルートに適用するなら
hooks.server.tsのhandleでまとめて設定する