ky の使い方 — Fetch APIベースの軽量HTTPクライアント
一言でいうと
kyは、ブラウザのFetch APIをラップした軽量かつエレガントなHTTPクライアントです。axiosのような便利なAPIを、わずか数KBのバンドルサイズで提供します。
どんな時に使う?
- フロントエンドアプリでREST APIを呼び出したい時 —
fetchの冗長な記述(response.okチェック、JSON.parseなど)を省略し、簡潔にHTTPリクエストを書きたい場合 - リトライ処理を手軽に実装したい時 — ネットワークエラーや5xxエラー時の自動リトライが組み込みで必要な場合
- axiosからの軽量化移行 — バンドルサイズを削減しつつ、同等の開発体験を維持したい場合
インストール
# npm
npm install ky
# yarn
yarn add ky
# pnpm
pnpm add ky
対応環境: ky v1.x はモダンブラウザ、Node.js 18.x以降、Deno、Bun で動作します。Fetch APIがネイティブで利用可能な環境が前提です。
基本的な使い方
import ky from 'ky';
// GETリクエスト — JSONを自動パース
const user = await ky.get('https://api.example.com/users/1').json<User>();
console.log(user.name);
// POSTリクエスト — bodyを自動的にJSON.stringifyしてくれる
const newUser = await ky
.post('https://api.example.com/users', {
json: { name: 'Taro', email: 'taro@example.com' },
})
.json<User>();
console.log(newUser.id);
// 型定義の例
interface User {
id: number;
name: string;
email: string;
}
fetchとの比較で見ると、kyの簡潔さが際立ちます:
// 素のfetchで書いた場合
const response = await fetch('https://api.example.com/users/1');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const user: User = await response.json();
// kyで書いた場合
const user = await ky.get('https://api.example.com/users/1').json<User>();
よく使うAPI
1. HTTPメソッドショートカット
import ky from 'ky';
// GET
const data = await ky.get('https://api.example.com/posts').json();
// POST
const created = await ky.post('https://api.example.com/posts', {
json: { title: 'Hello', body: 'World' },
}).json();
// PUT
await ky.put('https://api.example.com/posts/1', {
json: { title: 'Updated' },
});
// PATCH
await ky.patch('https://api.example.com/posts/1', {
json: { title: 'Patched' },
});
// DELETE
await ky.delete('https://api.example.com/posts/1');
2. searchParams — クエリパラメータの指定
// URLSearchParamsを手動で組み立てる必要がない
const results = await ky
.get('https://api.example.com/search', {
searchParams: {
q: 'typescript',
page: 2,
limit: 20,
},
})
.json<SearchResult>();
// => GET https://api.example.com/search?q=typescript&page=2&limit=20
3. retry — 自動リトライ
// デフォルトで2回リトライ(408, 413, 429, 500, 502, 503, 504 に対して)
const data = await ky
.get('https://api.example.com/unstable-endpoint', {
retry: {
limit: 3, // 最大3回リトライ
methods: ['get'], // GETのみリトライ
statusCodes: [503], // 503のみリトライ対象
backoffLimit: 3000, // バックオフの上限(ms)
},
})
.json();
4. hooks — リクエスト/レスポンスのインターセプト
const api = ky.create({
prefixUrl: 'https://api.example.com',
hooks: {
beforeRequest: [
(request) => {
const token = localStorage.getItem('accessToken');
if (token) {
request.headers.set('Authorization', `Bearer ${token}`);
}
},
],
beforeRetry: [
async ({ request, error, retryCount }) => {
console.log(`Retry #${retryCount} for ${request.url}`);
},
],
afterResponse: [
async (request, options, response) => {
if (response.status === 401) {
// トークンリフレッシュ処理
const newToken = await refreshToken();
request.headers.set('Authorization', `Bearer ${newToken}`);
return ky(request);
}
},
],
},
});
const user = await api.get('users/me').json<User>();
5. timeout と ky.create — インスタンスの共通設定
// プロジェクト全体で使う共通インスタンスを作成
const api = ky.create({
prefixUrl: 'https://api.example.com/v2',
timeout: 30_000, // 30秒(デフォルトは10秒)
headers: {
'X-Custom-Header': 'my-app',
},
retry: 2,
});
// 以降はprefixUrlからの相対パスで呼べる
const posts = await api.get('posts').json<Post[]>();
// => GET https://api.example.com/v2/posts
// インスタンスの設定を一部上書きして拡張
const adminApi = api.extend({
headers: {
'X-Admin': 'true',
},
});
類似パッケージとの比較
| 特徴 | ky | axios | got | node-fetch |
|---|---|---|---|---|
| バンドルサイズ | ~3 KB | ~13 KB | Node専用 | Node専用 |
| ベース | Fetch API | XMLHttpRequest | Node http | Fetch API polyfill |
| ブラウザ対応 | ✅ | ✅ | ❌ | ❌ |
| Node.js対応 | ✅ (18+) | ✅ | ✅ | ✅ |
| 自動リトライ | ✅ 組み込み | ❌ 別途必要 | ✅ 組み込み | ❌ |
| フック/インターセプター | ✅ | ✅ | ✅ | ❌ |
| JSON自動パース | .json() | デフォルト | .json() | .json() |
| TypeScript | ✅ ネイティブ | ✅ | ✅ | 要@types |
| ストリーミング | ✅ (ReadableStream) | 一部対応 | ✅ | ✅ |
選定の目安:
- ブラウザ中心で軽量にしたい → ky
- ブラウザ+Node.jsで実績重視 → axios
- Node.js専用で高機能 → got
注意点・Tips
HTTPErrorのハンドリング
kyはステータスコードが2xx以外の場合、自動的にHTTPErrorをスローします。エラーレスポンスのbodyを取得するには非同期処理が必要です。
import ky, { HTTPError } from 'ky';
try {
await ky.get('https://api.example.com/not-found').json();
} catch (error) {
if (error instanceof HTTPError) {
const statusCode = error.response.status;
const errorBody = await error.response.json<{ message: string }>();
console.error(`${statusCode}: ${errorBody.message}`);
}
}
prefixUrl の末尾スラッシュに注意
// ✅ 正しい — prefixUrlの末尾スラッシュは自動処理される
const api = ky.create({ prefixUrl: 'https://api.example.com/v1' });
await api.get('users'); // => https://api.example.com/v1/users
// ❌ 注意 — パス側を先頭スラッシュで始めるとprefixUrlが無視される
await api.get('/users'); // => これはエラーになる
FormDataの送信
const formData = new FormData();
formData.append('file', fileBlob, 'photo.png');
formData.append('description', 'プロフィール画像');
// Content-Typeは自動設定される(multipart/form-data)
await ky.post('https://api.example.com/upload', {
body: formData,
});
AbortControllerによるキャンセル
const controller = new AbortController();
// 5秒後にキャンセル
setTimeout(() => controller.abort(), 5000);
try {
const data = await ky
.get('https://api.example.com/large-data', {
signal: controller.signal,
})
.json();
} catch (error) {
if (error instanceof DOMException && error.name === 'AbortError') {
console.log('リクエストがキャンセルされました');
}
}
デフォルトのtimeoutは10秒
kyのデフォルトタイムアウトは10秒(10,000ms)です。fetchのデフォルト(タイムアウトなし)とは異なるため、長時間かかるAPIを呼ぶ場合は明示的にtimeoutを設定してください。falseを指定するとタイムアウトを無効化できます。
// タイムアウトを無効化
await ky.get('https://api.example.com/heavy', { timeout: false });
まとめ
kyは、Fetch APIの薄いラッパーとして「fetchに足りないもの」を的確に補うライブラリです。自動リトライ、フック、JSONの簡易ハンドリング、エラーの自動スローといった実用的な機能を、わずか数KBで提供します。特にフロントエンド開発において、axiosからの移行先として、あるいは素のfetchの改善策として、有力な選択肢となるでしょう。