
axios-fluentで型安全なHTTPクライアントをメソッドチェーンで構築する
はじめに
HTTPクライアントのコードを書いていると、設定の組み立てが冗長になりがちです。ベースURL、認証ヘッダー、Content-Type、タイムアウト...これらを毎回 axios.create() のオプションオブジェクトで管理するのは、コードの見通しが悪くなります。
axios-fluent は、Axiosをラップしてメソッドチェーンで設定を組み立てられるようにしたTypeScript製のHTTPクライアントライブラリです。Fluent Interface パターンにより、リクエストの構築から結果の取得までを一連のチェーンで書けます。
今回はこのライブラリを使って、実際にAPIクライアントを構築してみました。
前提・環境
- Node.js 20以上
- TypeScript 5.x
- axios 1.x(peerDependency)
- axios-fluent 2.1.1
インストール
pnpm add axios-fluent
axiosは peerDependency として定義されているため、プロジェクトにaxiosがまだない場合は一緒にインストールします。
pnpm add axios axios-fluent
基本的な使い方
クライアントの作成とGETリクエスト
import Axon from "axios-fluent";
const client = Axon.new();
// 従来のAxiosと同じように使える
const response = await client.get("https://api.example.com/users");
console.log(response.data);
Axon.new() でインスタンスを作成し、get() でリクエストを送ります。ここまでは普通のAxiosと変わりません。
.data() で結果を直接取得する
axios-fluent の便利な点は、レスポンスから必要な部分だけを取り出すメソッドが用意されていることです。
interface User {
id: number;
name: string;
email: string;
}
// .data() でレスポンスボディだけを取得
const users = await client.get<User[]>("https://api.example.com/users").data();
console.log(users); // User[] 型として取得できる
従来の response.data を取り出すパターンと比べて、コードがすっきりします。

他にも以下のメソッドが使えます。
// baseUrl 設定済みのクライアントの場合
// ステータスコードだけ取得
const status = await client.get("/users").status(); // 200
// レスポンスヘッダーだけ取得
const headers = await client.get("/users").headers();
// 2xx かどうかを確認
const isOk = await client.delete("/users/123").ok(); // true
メソッドチェーンで設定を組み立てる
このライブラリの中心的な機能がメソッドチェーンです。リクエストの設定を宣言的に積み重ねていけます。
const users = await Axon.new()
.baseUrl("https://api.example.com")
.bearer("your-jwt-token")
.json()
.timeout(5000)
.params({ page: 1, limit: 20 })
.get<User[]>("/users")
.data();
設定メソッドはすべて新しい Axon インスタンスを返します。つまりイミュータブルです。ベースとなるクライアントを作っておき、リクエストごとに設定を追加する使い方ができます。

// 共通設定を持つベースクライアント
const base = Axon.new()
.baseUrl("https://api.example.com")
.json()
.timeout(10000);
// 認証が必要なリクエスト用
const authed = base.bearer("your-token");
// 認証なしのリクエスト用(base は変更されない)
const publicData = await base.get<PublicInfo>("/public/info").data();
// 認証ありのリクエスト用
const privateData = await authed.get<PrivateInfo>("/me").data();
設定が副作用を持たないので、クライアントの使い回しが安全に行えます。
設定メソッド一覧
主な設定メソッドをまとめます。
認証
client.bearer("jwt-token"); // Authorization: Bearer xxx
client.basic(btoa("user:pass")); // Authorization: Basic xxx
Content-Type
client.json(); // application/json
client.multipart(); // multipart/form-data
client.encodeUrl(); // application/x-www-form-urlencoded
client.octet(); // application/octet-stream
その他
client.setHeader("X-API-Key", "secret"); // カスタムヘッダー
client.params({ page: 1 }); // クエリパラメータ
client.timeout(5000); // タイムアウト (ms)
client.responseType("blob"); // レスポンスタイプ
エラーハンドリング
axios-fluent では、Axiosのエラーが自動的に AxonError にラップされます。エラーの主要な情報に5つのプロパティでアクセスできます。
import Axon, { AxonError } from "axios-fluent";
try {
await Axon.new()
.baseUrl("https://api.example.com")
.get("/users/999")
.data();
} catch (error) {
if (error instanceof AxonError) {
console.log(error.status); // 404
console.log(error.statusText); // "Not Found"
console.log(error.url); // "/users/999"
console.log(error.method); // "GET"
console.log(error.responseData); // レスポンスボディ
// toString() でフォーマット済みメッセージを取得
console.log(error.toString());
// AxonError: Request failed with status code 404
// Request: GET /users/999
// Status: 404 Not Found
// Response: {"message":"User not found"}
}
}
ステータスコードに応じた分岐も直感的です。
if (error instanceof AxonError) {
if (error.status === 401) {
// トークンのリフレッシュ処理
} else if (error.status && error.status >= 500) {
// サーバーエラー - リトライ処理
}
}
実践例: APIクライアントクラスを作る
実際のプロジェクトで使う形で、APIクライアントクラスを組んでみます。
import Axon, { AxonError } from "axios-fluent";
interface User {
id: number;
name: string;
email: string;
}
class UserApiClient {
private client: Axon;
constructor(baseUrl: string, token: string) {
this.client = Axon.new()
.baseUrl(baseUrl)
.bearer(token)
.json()
.timeout(10000);
}
async getUser(id: number): Promise<User> {
return this.client.get<User>(`/users/${id}`).data();
}
async listUsers(page = 1, limit = 20): Promise<User[]> {
return this.client
.params({ page, limit })
.get<User[]>("/users")
.data();
}
async createUser(user: Omit<User, "id">): Promise<User> {
return this.client.post<User>("/users", user).data();
}
async deleteUser(id: number): Promise<boolean> {
return this.client.delete(`/users/${id}`).ok();
}
}
// 使い方
const api = new UserApiClient("https://api.example.com", "your-token");
const user = await api.getUser(1);
console.log(user.name);
const deleted = await api.deleteUser(1);
console.log(deleted); // true
設定メソッドのイミュータブル性のおかげで、listUsers のように params() を呼んでも、this.client の状態は変わりません。各メソッドが独立して動作します。
ファイルアップロード
multipart() とFormDataを組み合わせてファイルアップロードも可能です。
import Axon from "axios-fluent";
import FormData from "form-data";
const formData = new FormData();
formData.append("file", fileBuffer, "document.pdf");
await Axon.new()
.baseUrl("https://api.example.com")
.bearer("token")
.multipart()
.post("/upload", formData);
開発用モード
開発環境で自己署名証明書を使う場合は、Axon.dev() でSSL検証を無効化できます。
// 開発環境用 - 自己署名証明書を許可
const devClient = Axon.dev();
// 本番環境ではデフォルトのまま(SSL検証有効)
const prodClient = Axon.new();
Axon.dev() は Axon.new({ allowInsecure: true }) のショートカットです。本番環境では絶対に使わないよう注意してください。
設計上の特徴
実装を読んで気づいた設計上のポイントをいくつか挙げます。
イミュータブルな設定
各設定メソッドは内部で新しい Axon インスタンスを生成して返します。元のインスタンスは変更されないため、ベースクライアントを安全に再利用できます。
後方互換性
get() や post() の戻り値は AxonResponse<T> というラッパーですが、PromiseLike を実装しています。そのため await client.get("/url") とすれば従来通り AxiosResponse が返ります。.data() などの新しいメソッドはオプトインで使えるので、既存コードを壊しません。
AxonErrorによる構造化エラー
Axiosのエラーオブジェクトから必要な情報だけを抽出した5プロパティ(status, statusText, url, method, responseData)に絞ることで、エラーハンドリングのコードがシンプルになります。
まとめ
axios-fluent を使ってHTTPクライアントを構築してみました。
- メソッドチェーンにより、リクエストの設定を宣言的かつ読みやすく書ける
.data()/.status()/.ok()でレスポンスの必要な部分だけを簡潔に取り出せる- イミュータブルな設計で、ベースクライアントの安全な再利用が可能
AxonErrorによるエラーハンドリングの簡素化- 既存のAxiosコードとの後方互換性あり
Axiosを使っていてボイラープレートが気になっている方や、型安全なHTTPクライアントを手軽に構築したい方におすすめです。






