APIエンドポイント
+server.ts でバックエンドAPIを作る
APIエンドポイントとは?
日常での例え:レストランの厨房への注文窓口
注文窓口
お客さん(フロントエンド)が
「カレーください」と注文
→ 厨房が調理して提供
APIエンドポイント
クライアントが
「/api/users」にリクエスト
→ サーバーがデータを返す
なぜAPIエンドポイントが必要?
APIエンドポイントがないと困ること:
- データベースに直接アクセス?→ セキュリティ的に危険すぎる
- 外部サービス(決済など)の呼び出し→ APIキーが丸見えになる
- サーバーでしかできない処理が実行できない
APIエンドポイントがあれば:安全にデータのやり取りができる!
機密情報はサーバー側に隠したまま処理できます。
SvelteKitの良いところ
フロントエンドとバックエンドが同じプロジェクトで書ける!
別のサーバーを用意する必要がありません。
用語解説
API:アプリケーション同士がデータをやり取りするための窓口。
エンドポイント:APIの「住所」のこと。例:/api/users
GET:データを「取得」する時のリクエスト方法。
POST:データを「送信・作成」する時のリクエスト方法。
PUT / PATCH:データを「更新」する時のリクエスト方法。
DELETE:データを「削除」する時のリクエスト方法。
JSON:データ交換用の形式。{"name": "太郎", "age": 20} のような書き方。
ステータスコード:リクエストの結果を表す番号。200=成功、404=見つからない、500=サーバーエラー
+server.ts の場所
src/routes/api/users/+server.ts
→ /api/users でアクセス可能
基本的なAPI
GETリクエスト
データの取得
typescript
// src/routes/api/users/+server.ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
// GET /api/users
export const GET: RequestHandler = async () => {
const users = [
{ id: 1, name: '田中太郎' },
{ id: 2, name: '鈴木花子' }
];
return json(users);
};
// レスポンス:
// [{"id":1,"name":"田中太郎"},{"id":2,"name":"鈴木花子"}]プレビュー
GET /api/users
// ユーザー一覧を返す
POSTリクエスト
データの作成
typescript
// src/routes/api/users/+server.ts
import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
// POST /api/users
export const POST: RequestHandler = async ({ request }) => {
// リクエストボディを取得
const body = await request.json();
// バリデーション
if (!body.name || !body.email) {
throw error(400, 'name と email は必須です');
}
// DBに保存(例)
const newUser = await db.user.create({
data: {
name: body.name,
email: body.email
}
});
// 201 Created で返す
return json(newUser, { status: 201 });
};プレビュー
POST /api/users
// 新しいユーザーを作成
他のHTTPメソッド
PUT, PATCH, DELETE
typescript
// src/routes/api/users/[id]/+server.ts
import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
// GET /api/users/:id
export const GET: RequestHandler = async ({ params }) => {
const user = await db.user.findUnique({
where: { id: Number(params.id) }
});
if (!user) {
throw error(404, 'ユーザーが見つかりません');
}
return json(user);
};
// PUT /api/users/:id (全体更新)
export const PUT: RequestHandler = async ({ params, request }) => {
const body = await request.json();
const user = await db.user.update({
where: { id: Number(params.id) },
data: body
});
return json(user);
};
// PATCH /api/users/:id (部分更新)
export const PATCH: RequestHandler = async ({ params, request }) => {
const body = await request.json();
const user = await db.user.update({
where: { id: Number(params.id) },
data: body
});
return json(user);
};
// DELETE /api/users/:id
export const DELETE: RequestHandler = async ({ params }) => {
await db.user.delete({
where: { id: Number(params.id) }
});
return new Response(null, { status: 204 });
};プレビュー
GET - 取得
PUT - 全体更新
PATCH - 部分更新
DELETE - 削除
リクエスト情報の取得
様々なリクエスト情報
event オブジェクトから取得
typescript
export const GET: RequestHandler = async (event) => {
// URLパラメータ: /api/users/123 → params.id = "123"
const { id } = event.params;
// クエリパラメータ: /api/users?page=2&limit=10
const page = event.url.searchParams.get('page') || '1';
const limit = event.url.searchParams.get('limit') || '10';
// リクエストヘッダー
const authHeader = event.request.headers.get('Authorization');
// Cookie
const session = event.cookies.get('session');
// ローカル(hooksで設定した値)
const user = event.locals.user;
return json({ page, limit });
};プレビュー
params - URLパラメータ
url.searchParams - クエリ
request.headers - ヘッダー
cookies - Cookie
FormDataの処理
ファイルアップロードなど
typescript
// src/routes/api/upload/+server.ts
export const POST: RequestHandler = async ({ request }) => {
const formData = await request.formData();
// テキストフィールド
const title = formData.get('title');
// ファイル
const file = formData.get('file') as File;
if (file) {
const buffer = await file.arrayBuffer();
// ファイルを保存...
console.log('ファイル名:', file.name);
console.log('サイズ:', file.size);
console.log('タイプ:', file.type);
}
return json({ success: true });
};プレビュー
// FormDataでファイルを受け取る
// request.formData() で取得
レスポンスの種類
様々なレスポンス
JSON以外も返せる
typescript
import { json, text, error, redirect } from '@sveltejs/kit';
// JSON レスポンス
export const GET: RequestHandler = async () => {
return json({ message: 'Hello' });
};
// テキスト レスポンス
export const GET: RequestHandler = async () => {
return text('Hello, World!');
};
// エラー レスポンス
export const GET: RequestHandler = async () => {
throw error(404, 'Not Found');
};
// リダイレクト
export const GET: RequestHandler = async () => {
throw redirect(302, '/login');
};
// カスタムレスポンス
export const GET: RequestHandler = async () => {
return new Response('<h1>HTML</h1>', {
status: 200,
headers: {
'Content-Type': 'text/html'
}
});
};プレビュー
json() - JSON
text() - テキスト
error() - エラー
redirect() - リダイレクト
フロントエンドからの呼び出し
fetchでAPIを呼ぶ
Svelteコンポーネントから
svelte
<script>
let users = $state([]);
let loading = $state(false);
let error = $state('');
async function loadUsers() {
loading = true;
error = '';
try {
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error('データの取得に失敗しました');
}
users = await response.json();
} catch (e) {
error = e.message;
} finally {
loading = false;
}
}
async function createUser() {
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: '新しいユーザー',
email: 'new@example.com'
})
});
if (response.ok) {
await loadUsers(); // リストを更新
}
}
// 初回読み込み
$effect(() => {
loadUsers();
});
</script>
{#if loading}
<p>読み込み中...</p>
{:else if error}
<p class="error">{error}</p>
{:else}
<ul>
{#each users as user}
<li>{user.name}</li>
{/each}
</ul>
{/if}
<button onclick={createUser}>ユーザー追加</button>プレビュー
- 田中太郎
- 鈴木花子
認証付きAPI
認証チェック
localsを使った認証
typescript
// src/routes/api/protected/+server.ts
import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const GET: RequestHandler = async ({ locals }) => {
// hooksで設定したユーザー情報をチェック
if (!locals.user) {
throw error(401, '認証が必要です');
}
// 権限チェック
if (locals.user.role !== 'admin') {
throw error(403, '権限がありません');
}
// 保護されたデータを返す
const secretData = await getSecretData();
return json(secretData);
};
// src/hooks.server.ts
export const handle: Handle = async ({ event, resolve }) => {
const session = event.cookies.get('session');
if (session) {
event.locals.user = await getUserFromSession(session);
}
return resolve(event);
};プレビュー
// hooks.server.ts でユーザーを設定
// API側で locals.user をチェック
ベストプラクティス
✅ やるべきこと
- バリデーションを必ず行う
- 適切なステータスコードを返す
- エラーメッセージを分かりやすく
- 型定義をしっかり書く
❌ 避けるべきこと
- ユーザー入力を信用する
- 機密情報をレスポンスに含める
- エラーの詳細を本番で露出
- 認証なしで機密データにアクセス