SvelteKitの+server.tsでAPIエンドポイントを作った話
SvelteKitでは +server.ts を使うとREST APIのエンドポイントを作れます。load 関数や actions ではカバーできない場面で使います。
+server.ts が必要な場面
load / actions で対応できる → +page.server.ts を使う
それ以外(APIとして公開したい) → +server.ts を使う
具体的には:
- 外部サービスからの Webhook の受信
- JSONを返すAPIエンドポイント(外部から叩かれる)
- ファイルダウンロード(CSV・PDFなど)
- クライアントから
fetchで直接叩くエンドポイント
① 基本的な書き方
HTTPメソッド名の関数をエクスポートします。
// src/routes/api/posts/+server.ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
// GET /api/posts
export const GET: RequestHandler = async ({ platform }) => {
const result = await platform!.env.DB
.prepare('SELECT * FROM posts')
.all();
return json(result.results);
};
// POST /api/posts
export const POST: RequestHandler = async ({ request, platform }) => {
const body = await request.json();
await platform!.env.DB
.prepare('INSERT INTO posts (title, content) VALUES (?, ?)')
.bind(body.title, body.content)
.run();
return json({ success: true }, { status: 201 });
};
② クライアントから叩く
fetch は +page.svelte の <script> 内に書きます。「ボタン操作などユーザーのアクションに応じてデータを取得・更新したいとき」が主な用途です。ページ表示時に自動取得するだけなら load 関数を使う方が適切です。
<!-- src/routes/learn/+page.svelte -->
<script lang="ts">
let apiPosts = $state<any[]>([]);
async function fetchPostsFromApi() {
const res = await fetch('/api/posts');
apiPosts = await res.json();
}
</script>
<button onclick={fetchPostsFromApi}>APIから記事を取得</button>
<ul>
{#each apiPosts as post}
<li>{post.title}</li>
{/each}
</ul>
| タイミング | 書き方 |
|---|---|
| ボタン押下時 | onclick={fetchPostsFromApi} で呼ぶ |
| ページ表示時に自動取得 | $effect(() => { fetchPostsFromApi(); }) で呼ぶ |
| 別の処理の後 | 関数の中で await fetchPostsFromApi() と呼ぶ |
③ エラーレスポンスを返す
IDで1件取得するなど、リソースが見つからない場合のエラー処理はURLごとに別ファイルに書きます。
src/routes/api/posts/+server.ts ← 一覧・追加
src/routes/api/posts/[id]/+server.ts ← 1件取得・エラー処理
// src/routes/api/posts/[id]/+server.ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
// GET /api/posts/1
export const GET: RequestHandler = async ({ params, platform }) => {
const post = await platform!.env.DB
.prepare('SELECT * FROM posts WHERE id = ?')
.bind(params.id)
.first();
if (!post) {
return json({ error: '記事が見つかりません' }, { status: 404 });
}
return json(post);
};
④ JSON 以外のレスポンスを返す(CSVダウンロード)
CSVなどJSON以外のレスポンスは new Response() を使って返します。
src/routes/api/posts/export/+server.ts ← CSVダウンロード
// src/routes/api/posts/export/+server.ts
import type { RequestHandler } from './$types';
// GET /api/posts/export
export const GET: RequestHandler = async ({ platform }) => {
const result = await platform!.env.DB
.prepare('SELECT * FROM posts')
.all();
const csv = [
'id,title', // ヘッダー行
...result.results.map((p: any) => `${p.id},${p.title}`)
].join('\n');
return new Response(csv, {
headers: {
'Content-Type': 'text/csv',
'Content-Disposition': 'attachment; filename="posts.csv"',
},
});
};
CSVダウンロードは fetch ではなく <a href="..." download> で呼ぶのがシンプルです。
<a href="/api/posts/export" download>CSVダウンロード</a>
Content-Disposition: attachment がついているのでブラウザがダウンロードとして扱い、posts.csv として保存されます。
+page.server.ts(actions)と +server.ts の使い分け
+page.server.ts(actions) |
+server.ts |
|
|---|---|---|
| 呼び出し元 | 同じページのフォーム | どこからでも(外部含む) |
use:enhance |
使える | 使えない |
| レスポンス形式 | SvelteKit の形式 | 自由(JSON・CSV など) |
| 向いているケース | フォーム送信 | API・Webhook・ファイル出力 |
フォーム送信には actions、それ以外のAPIには +server.ts と覚えておくとシンプルです。