現代のアプリケーション開発の世界では、REST APIは基本的な通信レイヤーとして機能し、異なるシステム間でのデータのシームレスな交換を可能にしています。アプリケーションの規模と複雑さが増すにつれて、扱うデータの量も増加します。数百万、あるいは数十億のレコードを含む可能性のあるデータセット全体を単一のAPIコールで要求することは、非効率的で信頼性が低く、重大なパフォーマンスボトルネックとなります。ここで、API設計と開発における重要なテクニックが登場します。それがREST APIのページネーションです。このガイドでは、REST APIにおけるページネーションの実装について、基本的な概念からNode.js、Python、.NETなどの様々なテクノロジースタックを使用した高度な実践的な実装まで、深く包括的な概要を提供します。
最大限の生産性で開発チームが協力して作業できる、統合されたオールインワンプラットフォームをお探しですか?
Apidogはこれらの要望をすべて満たし、Postmanよりもはるかに手頃な価格で代替できます!
REST APIページネーションの基礎
複雑なコード例や設計パターンに入る前に、ページネーションとは何か、そしてなぜそれがプロフェッショナルなAPI設計において必須の要素なのかをしっかりと理解しておくことが不可欠です。
REST APIにおけるページネーションとは?
本質的に、REST APIページネーションは、REST APIエンドポイントのレスポンスを、より小さく管理しやすい単位(しばしば「ページ」と呼ばれる)に分割するために使用されるテクニックです。潜在的に膨大なデータセットを一度に配信する代わりに、APIは小さく予測可能なデータの塊を返します。重要なのは、APIレスポンスには、クライアントが必要に応じて後続の塊を増分的にフェッチできるようにするメタデータも含まれていることです。
このプロセスは、本のページやGoogleの検索結果に似ています。最初の検索結果ページが表示され、それに加えて2ページ目、3ページ目などへ移動するためのコントロールが提供されます。DEV Communityのような開発者コミュニティやMerge.devのようなプラットフォームが指摘するように、これは大きなデータセットを小さな塊に分割するプロセスであり、クライアントが本当にすべてのデータを必要とする場合に増分的にフェッチできます。これは、堅牢でスケーラブルなアプリケーションを構築するための基礎的な概念です。
なぜページネーションは現代のAPI設計における中核的な要件なのか?
ページネーションの主な動機は、APIレスポンスをサーバーとクライアントの両方にとって扱いやすくすることです。それがなければ、アプリケーションは深刻な制限と貧弱なユーザーエクスペリエンスに直面するでしょう。主な利点は以下の通りです。
- パフォーマンスの向上と遅延の削減: 最大の利点は速度です。25レコードの小さなJSONペイロードを転送することは、250万レコードのペイロードを転送することよりも桁違いに高速です。これにより、エンドユーザーにとってキビキビとしたレスポンスの良い感覚が生まれます。
- APIの信頼性の向上: 大規模なHTTPレスポンスは、ネットワークタイムアウト、接続切断、またはクライアント側のメモリ制限により、転送途中で失敗する可能性が高くなります。ページネーションは、より小さく、より回復力のあるリクエストを作成します。あるページのロードが失敗した場合、クライアントはデータ転送全体を最初からやり直す必要なく、その特定のリクエストを単に再試行できます。
- サーバー負荷の軽減: 大規模なレスポンスを生成することは、サーバーのリソースに大きな負担をかける可能性があります。データベースクエリが遅くなる可能性があり、数百万のレコードをJSONにシリアライズすることはかなりのCPUとメモリを消費します。ページネーションにより、サーバーはより小さく、より効率的なクエリを実行でき、全体的な容量と複数のクライアントに同時にサービスを提供する能力が向上します。
- クライアント側処理の効率化: クライアントアプリケーション、特にモバイルデバイスやウェブブラウザで実行されるアプリケーションにとって、巨大なJSONオブジェクトを解析することはユーザーインターフェースをフリーズさせ、フラストレーションのたまるエクスペリエンスにつながる可能性があります。データの小さな塊は、解析とレンダリングが容易であり、よりスムーズなアプリケーションになります。
一般的なページネーション戦略とテクニック
ページネーションを実装する方法はいくつかありますが、2つの主要な戦略が業界のデファクトスタンダードとなっています。それらの選択は、パフォーマンス、データの一貫性、およびユーザーエクスペリエンスに大きな影響を与えます。
オフセットベースのページネーション:基本的なアプローチ
オフセットベースのページネーションは、「ページ番号ページネーション」とも呼ばれ、開発者が最初に学ぶアプローチであることがよくあります。概念的にシンプルで、多くのウェブアプリケーションで見られます。これは、2つの主要なパラメータを使用して機能します。
limit
(またはpage_size
): 1ページで返す最大結果数。offset
(またはpage
): データセットの先頭からスキップするレコード数。page
パラメータを使用する場合、オフセットは通常(page - 1) * limit
として計算されます。
一般的なリクエストは次のようになります: GET /api/products?limit=25&offset=50
これは、次のようなSQLクエリに変換されます。SQL
SELECT * FROM products ORDER BY created_at DESC LIMIT 25 OFFSET 50;
このクエリは、最初の50個の製品をスキップし、次の25個(つまり、製品51〜75)を取得します。
長所:
- シンプルさ: この方法は実装が簡単であり、「Node.js REST API: Offset Pagination Made Easy」のような多くのチュートリアルで示されています。
- ステートレスなナビゲーション: クライアントは、事前の情報なしにデータセット内の任意のページに簡単にジャンプでき、番号付きページリンクを持つUIに最適です。
短所と制限:
- 大規模データセットでのパフォーマンス低下: 主な欠点は、データベースの
OFFSET
句です。大きなオフセットを持つリクエスト(例:OFFSET 1000000
)の場合、データベースは依然としてディスクから1,000,025件すべてのレコードをフェッチし、最初の100万件をスキップするためにカウントし、その後ようやく最後の25件を返さなければなりません。これは、ページ番号が増加するにつれて信じられないほど遅くなる可能性があります。 - データの一貫性の欠如(ページドリフト): ユーザーがページング中に新しいレコードがデータベースに書き込まれると、データセット全体がシフトします。ユーザーが2ページ目から3ページ目に移動すると、2ページ目の終わりからレコードが繰り返されたり、レコードが完全に失われたりする可能性があります。これはリアルタイムアプリケーションにとって重大な問題であり、データの一貫性を確保する方法についてStack Overflowのような開発者フォーラムでよく議論されるトピックです。
カーソルベース(キーセット)ページネーション:スケーラブルなソリューション
カーソルベースのページネーションは、キーセットまたはシークページネーションとも呼ばれ、オフセットメソッドのパフォーマンスと一貫性の問題を解決します。ページ番号の代わりに、データセット内の特定のレコードへの安定した不透明なポインターである「カーソル」を使用します。
フローは次のとおりです。
- クライアントはデータのページを要求する最初のリクエストを行います。
- サーバーはデータのページを返し、そのセットの最後の項目を指すカーソルを含めます。
- 次のページのために、クライアントはそのカーソルをサーバーに送り返します。
- サーバーは、その特定のカーソルの後に来るレコードを取得し、データセット内のそのポイントに効果的に「シーク」します。
カーソルは通常、ソート対象の列から派生したエンコードされた値です。たとえば、created_at
(タイムスタンプ)でソートする場合、カーソルは最後のレコードのタイムスタンプになる可能性があります。タイを処理するために、2番目のユニークな列(レコードのid
など)がしばしば含まれます。
カーソルを使用したリクエストは次のようになります: GET /api/products?limit=25&after_cursor=eyJjcmVhdGVkX2F0IjoiMjAyNS0wNi0wN1QxODowMDowMC4wMDBaIiwiaWQiOjg0N30=
これは、はるかにパフォーマンスの高いSQLクエリに変換されます。SQL
SELECT * FROM products
WHERE (created_at, id) < ('2025-06-07T18:00:00.000Z', 847)
ORDER BY created_at DESC, id DESC
LIMIT 25;
このクエリは、(created_at, id)
のインデックスを使用して正しい開始点に瞬時に「シーク」し、フルテーブルスキャンを回避し、ユーザーがどれだけ深くページングしても一貫して高速になります。
長所:
- 高いパフォーマンスとスケーラビリティ: データベースのパフォーマンスは高速で一定であり、あらゆるサイズのデータセットに適しています。
- データの一貫性: カーソルは絶対的な位置ではなく特定のレコードに結びついているため、新しいデータが追加または削除されても、ページ間で項目が失われたり繰り返されたりすることはありません。
短所:
- 実装の複雑さ: カーソルの生成と解析のロジックは、単純なオフセット計算よりも複雑です。
- 制限されたナビゲーション: クライアントは「次」または「前」のページにのみ移動できます。特定のページ番号に直接ジャンプすることはできないため、特定のUIパターンにはあまり適していません。
- 安定したソートキーが必要: 実装はソート順に密接に関連しており、少なくとも1つのユニークで連続した列が必要です。
2つの主要なページネーションタイプの比較
オフセットページネーションとカーソルページネーションのどちらを選択するかは、ユースケースに完全に依存します。
機能 | オフセットページネーション | カーソルページネーション |
パフォーマンス | 大規模データセットの深いページではパフォーマンスが低下します。 | あらゆる深さで優れており、一貫しています。 |
データの一貫性 | データの欠落/重複(ページドリフト)が発生しやすいです。 | 高いです。新しいデータはページネーションに影響しません。 |
ナビゲーション | 任意のページにジャンプできます。 | 次/前のページに制限されます。 |
実装 | シンプルで分かりやすいです。 | より複雑です。カーソルロジックが必要です。 |
理想的なユースケース | 小規模で静的なデータセット、管理UI。 | 無限スクロールフィード、大規模で動的なデータセット。 |
サーバーサイドページネーションの実装におけるベストプラクティス
選択した戦略に関わらず、一連のベストプラクティスに従うことで、クリーンで予測可能、かつ使いやすいAPIが実現します。これは、「サーバーサイドページネーションのベストプラクティスとは?」という問いに対する重要な回答の一部となることがよくあります。
ページネーションレスポンスペイロードの設計
よくある間違いは、結果の配列だけを返すことです。適切に設計されたページネーションレスポンスペイロードは、データを「包み込み」、明確なページネーションメタデータを含むオブジェクトであるべきです。JSON
{
"data": [
{ "id": 101, "name": "Product A" },
{ "id": 102, "name": "Product B" }
],
"pagination": {
"next_cursor": "eJjcmVhdGVkX2F0Ij...",
"has_next_page": true
}
}
オフセットページネーションの場合、メタデータは異なります。JSON
{
"data": [
// ... 結果 ...
],
"metadata": {
"total_results": 8452,
"total_pages": 339,
"current_page": 3,
"per_page": 25
}
}
この構造により、クライアントはさらにフェッチするデータがあるかどうか、またはUIコントロールをレンダリングする必要があるかどうかを簡単に知ることができます。
ナビゲーションのためのハイパーメディアリンクの使用(HATEOAS)
RESTの核心原則の1つはHATEOAS(Hypermedia as the Engine of Application State)です。これは、APIがクライアントに他のリソースやアクションにナビゲートするためのリンクを提供すべきであることを意味します。ページネーションの場合、これは信じられないほど強力です。GitHub Docsで示されているように、これを行うための標準的な方法は、Link
HTTPヘッダーを使用することです。
Link: <https://api.example.com/items?page=3>; rel="next", <https://api.example.com/items?page=1>; rel="prev"
あるいは、これらのリンクをJSONレスポンスボディに直接配置することもできます。これはJavaScriptクライアントが消費しやすい場合が多いです。JSON
"pagination": {
"links": {
"next": "https://api.example.com/items?limit=25&offset=75",
"previous": "https://api.example.com/items?limit=25&offset=25"
}
}
これにより、クライアントはURLを手動で構築する必要がなくなります。
クライアントによるページサイズの制御を許可する
クライアントがページ分割されたレスポンスに対して追加のページの結果を要求すること、および各ページで返される結果の数を変更することを許可することは良い習慣です。これは通常、limit
または per_page
クエリパラメータで行われます。ただし、サーバーは常に合理的な最大制限(例:100)を強制し、クライアントが一度に多すぎるデータを要求してシステムを過負荷にしないようにする必要があります。
ページネーションとフィルタリングおよびソートの組み合わせ
実際のAPIはページネーションだけを行うことはめったにありません。フィルタリングとソートもサポートする必要があります。.NETのようなテクノロジーを扱うチュートリアルで示されているように、これらの機能を追加することは一般的な要件です。
複雑なリクエストは次のようになる可能性があります: GET /api/products?status=published&sort=-created_at&limit=50&page=2
これを実装する際には、フィルタリングとソートのパラメータがページネーションロジックの一部として考慮されることが重要です。ページネーションが正しく機能するためには、sort
順序が安定しており決定的である必要があります。ソート順序が一意でない場合は、ページ間の一貫した順序付けを確保するために、ユニークなタイブレーカー列(id
など)を追加する必要があります。
実践的な実装例
さまざまな一般的なフレームワークでこれらの概念を実装する方法を見ていきましょう。
Django REST Frameworkを使用したPythonでのREST APIページネーション
API構築で最も人気のある組み合わせの1つは、PythonとDjango REST Framework (DRF)です。DRFは、ページネーションのための強力な組み込みサポートを提供し、開始を信じられないほど容易にします。さまざまな戦略のためのクラスを提供しています。
PageNumberPagination
: 標準的なページ番号ベースのオフセットページネーション用。LimitOffsetPagination
: より柔軟なオフセット実装用。CursorPagination
: 高パフォーマンスなカーソルベースのページネーション用。
デフォルトのページネーションスタイルをグローバルに設定し、汎用のListAPIView
を使用するだけで、残りはDRFが処理します。これは、Rest api pagination pythonの主要な例です。Python
# settings.py内
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.CursorPagination',
'PAGE_SIZE': 50
}
# views.py内
class ProductListView(generics.ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
# DRFがページネーションロジック全体を自動的に処理します!
Node.js、Express、およびTypeScriptを使用したページネーションREST APIの構築
Node.jsエコシステムでは、ページネーションロジックを手動で構築することが多く、これにより完全な制御が可能になります。このガイドセクションでは、Node.js、Express、およびTypeScriptを使用したページネーション構築の概念的な概要を提供します。
カーソルページネーションを実装する単純な例を以下に示します。TypeScript
// Expressコントローラー内
app.get('/products', async (req: Request, res: Response) => {
const limit = parseInt(req.query.limit as string) || 25;
const cursor = req.query.cursor as string;
let query = db.selectFrom('products').orderBy('createdAt', 'desc').orderBy('id', 'desc').limit(limit);
if (cursor) {
const { createdAt, id } = JSON.parse(Buffer.from(cursor, 'base64').toString('ascii'));
// カーソルのためのWHERE句を追加
query = query.where('createdAt', '<=', createdAt).where('id', '<', id);
}
const products = await query.execute();
const nextCursor = products.length > 0
? Buffer.from(JSON.stringify({
createdAt: products[products.length - 1].createdAt,
id: products[products.length - 1].id
})).toString('base64')
: null;
res.json({
data: products,
pagination: { next_cursor: nextCursor }
});
});
Javaまたは.NETエコシステムにおけるページネーション
他のエコシステムのフレームワークも、堅牢なページネーションサポートを提供しています。
- Java (Spring Boot): Spring Dataプロジェクトはページネーションを非常に簡単にします。
PagingAndSortingRepository
を使用することで、Page<Product> findAll(Pageable pageable);
のようなメソッドシグネチャを定義できます。Springはメソッドを自動的に実装し、page
、size
、sort
のリクエストパラメータを処理し、結果と必要なすべてのページネーションメタデータを含むPage
オブジェクトを返します。これは、「Java REST APIでページネーションを実装する方法は?」に対するベストプラクティスの回答です。 - .NET: .NETの世界では、開発者はオフセットページネーションを実装するために、
.Skip()
や.Take()
のようなメソッドを持つIQueryable
拡張機能を使用することがよくあります。より高度なシナリオでは、ライブラリが効率的なSQLクエリに変換されるカーソルベースのソリューションの構築を支援できます。
### 実践的なユースケース:製品カタログAPIのページネーション
「製品カタログAPI」を持つEコマースウェブサイトを考えてみましょう。これは完璧な実践的なユースケースです。カタログは大きく動的であり、新しい製品が頻繁に追加されます。
- 問題: サイトが製品リストにオフセットページネーションを使用しており、顧客が1ページ目から2ページ目に閲覧中に新しい製品が追加された場合、顧客は1ページ目の最後の製品が2ページ目の先頭で繰り返されるのを見る可能性があります。これはユーザーエクスペリエンスを混乱させます。
- 解決策: カーソルベースのページネーションを実装することが理想的な解決策です。フロントエンドの「もっと見る」ボタンは、最後に表示された製品のカーソルを渡します。APIは、その特定の製品の後の次の製品セットを返し、ユーザーにとってリストが重複したり欠落したりすることなく単純に増加することを保証します。
高度なトピックと一般的な問題
Stack OverflowやRedditの開発者がよく発見するように、真に堅牢なページネーションシステムを構築するには、多くの詳細とエッジケースの処理が必要です。
ページ分割されたAPIでデータの一貫性を確保する方法
これは最も重要な高度なトピックの1つです。前述のように、頻繁な書き込みがあるシステムでデータの一貫性を保証する唯一の信頼できる方法は、キーセット/カーソルページネーションを使用することです。その設計は本質的にページドリフトを防ぎます。何らかの理由でオフセットページネーションに固執する必要がある場合、一時的で不変のIDスナップショットを作成し、そのリストをページングするなどの複雑な回避策が存在しますが、これは非常にステートフルであり、一般的にREST APIには推奨されません。
奇妙なエッジケースの処理
プロダクション対応のAPIは、不正な入力を適切に処理する必要があります。これらの一般的なエッジケースを考慮してください。
- クライアントが
page=0
またはoffset=-50
を要求する。APIは500エラーをスローすべきではありません。明確なエラーメッセージとともに400 Bad Request
を返す必要があります。 - クライアントが不正な形式または無効な
cursor
を提供する。APIは再び400 Bad Request
を返す必要があります。 - クライアントが有効なカーソルを提供するが、それが指す項目が削除されている。良い戦略は、カーソルがその項目があった「場所」を指しているとみなし、その時点から次の結果ページを返すことです。
クライアントサイドの実装
クライアントサイドは、ページネーションロジックが消費される場所です。JavaScriptを使用してプロのようにREST APIからページ分割されたデータをフェッチするには、ページネーションメタデータを読み取り、それを使用して後続のリクエストを行うことが含まれます。
カーソルページネーションを使用した「もっと見る」ボタンの単純なfetch
例を以下に示します。JavaScript
const loadMoreButton = document.getElementById('load-more');
let nextCursor = null; // カーソルをグローバルまたはコンポーネントの状態に保存
async function fetchProducts(cursor) {
const url = cursor ? `/api/products?cursor=${cursor}` : '/api/products';
const response = await fetch(url);
const data = await response.json();
// ... 新しい製品をレンダリング ...
nextCursor = data.pagination.next_cursor;
if (!nextCursor) {
loadMoreButton.disabled = true; // これ以上ページはありません
}
}
loadMoreButton.addEventListener('click', () => fetchProducts(nextCursor));
// 初回ロード
fetchProducts(null);
APIデータ取得とページネーション標準の未来
RESTは何年も支配的でしたが、状況は常に進化しています。
進化するREST APIページネーション標準
REST APIページネーション標準を定義する単一の正式なRFCはありません。しかし、GitHub、Stripe、Atlassianのような主要なテクノロジー企業の公開APIによって推進される、強力な慣習のセットが出現しています。Link
ヘッダーの使用や明確なメタデータの提供といったこれらの慣習は、デファクトスタンダードとなっています。一貫性が鍵であり、適切に設計されたAPIプラットフォームは、すべてのリストベースのエンドポイントで同じページネーション戦略を使用します。
GraphQLがページネーションに与える影響
GraphQLは異なるパラダイムを提示します。複数のエンドポイントの代わりに、クライアントが必要な正確なデータを指定する複雑なクエリを送信する単一のエンドポイントがあります。しかし、大規模なデータリストをページングする必要性はなくなりません。GraphQLコミュニティも、Relay Cursor Connections Specと呼ばれる正式な仕様を通じて、カーソルベースのページネーションを標準化しています。これは、first
、after
、last
、before
のような概念を使用して、堅牢な前方および後方ページネーションを提供するデータページネーションの正確な構造を定義します。
結論:ページネーションのベストプラクティスのまとめ
REST APIページネーションを習得することは、あらゆるバックエンド開発者にとって重要なスキルです。これは、スケーラブルでパフォーマンスが高く、ユーザーフレンドリーなアプリケーションを構築するために不可欠なテクニックです。
REST APIページネーションのベストプラクティスをまとめると、以下のようになります。
- 常にページネーションを行う: APIエンドポイントから無制限の結果リストを返さないでください。
- 適切な戦略を選択する: 小規模で重要でない、または静的なデータセットには単純なオフセットページネーションを使用します。大規模で動的、またはユーザー向けのデータには、優れたパフォーマンスとデータの一貫性のために、カーソルベースのページネーションを強く推奨します。
- 明確なメタデータを提供する: レスポンスペイロードには、
next_cursor
またはページ番号とリンクなど、クライアントが次のデータページを取得する方法を示す情報が常に含まれている必要があります。 - ハイパーメディアを使用する:
Link
ヘッダーまたはJSONボディ内のリンクを使用して、APIをより発見しやすく、使いやすくします。 - エラーを適切に処理する: すべてのページネーションパラメータを検証し、無効な入力に対して明確な
400 Bad Request
エラーを返します。
このガイドに従い、これらの原則を内面化することで、あらゆる要求に効果的に対応できるプロフェッショナルでプロダクション対応のREST APIを設計および構築できます。
REST APIページネーションに関するよくある質問
1. オフセットページネーションとカーソルページネーションの主な違いは何ですか?
主な違いは、どのデータセットを取得するかを決定する方法にあります。オフセットページネーションは、数値のオフセット(「最初の50項目をスキップ」など)を使用して次のページを見つけます。これは、データベースがスキップされる項目をカウントする必要があるため、大規模なデータセットでは遅くなる可能性があります。カーソルページネーションは、特定のレコードを指す安定したポインターまたは「カーソル」(「製品ID 857の後の項目を取得」など)を使用します。データベースはインデックスを使用してそのレコードに直接ジャンプできるため、これははるかに効率的です。
2. カーソルページネーションの代わりにオフセットページネーションを使用するのが適切なのはどのような場合ですか?
オフセットページネーションは、データセットが小規模で、パフォーマンスが重要でなく、頻繁に変更されない場合に適しています。その主な利点はシンプルさと、ユーザーが任意の特定のページ番号にジャンプできること(例:「10ページ目に移動」)です。これは、ページ間のジャンプというユーザーエクスペリエンスがリアルタイムデータの変更処理よりも重要な管理ダッシュボードや内部ツールなどに適しています。
3. カーソルベースのページネーションは、項目のスキップや重複の問題をどのように防ぎますか?
カーソルベースのページネーションは、次のリクエストを数値位置ではなく特定の項目に固定するため、データの一貫性を防ぎます。たとえば、ID=100
の項目の後のページを要求する場合、その前に新しい項目が追加されても関係ありません。クエリは常に正しい場所からフェッチを開始します。オフセットページネーションでは、表示中に1ページ目に新しい項目が追加されると、2ページ目を要求したときに、1ページ目の最後の項目が2ページ目の最初の項目になり、重複が発生します。
4. REST APIページネーションレスポンスに関する公式な標準はありますか?
すべてのREST APIページネーションがどのように実装されるべきかを規定する単一の公式RFCまたは正式な標準はありません。しかし、GitHubやStripeのような主要な公開APIによって、業界から強力な慣習とベストプラクティスが出現しています。これらの慣習には、rel="next"
およびrel="prev"
属性を持つLink
HTTPヘッダーの使用、またはJSONレスポンスボディに明確なメタデータとリンクを含むpagination
オブジェクトを埋め込むことが含まれます。
5. ページ分割されたエンドポイントでソートとフィルタリングをどのように処理すべきですか?
ソートとフィルタリングはページネーションの前に適用されるべきです。ページ分割された結果は、すでにソートおよびフィルタリングされたデータセットへの「ビュー」であるべきです。ソート順序が安定しており決定的であることが重要です。ユーザーが一意でないフィールド(日付など)でソートする場合、タイブレーカーとして機能する一意のセカンダリソートキー(レコードのid
など)を追加する必要があります。これにより、項目の順序が常に同じになり、オフセットページネーションとカーソルページネーションの両方が正しく機能するために不可欠です。
最大限の生産性で開発チームが協力して作業できる、統合されたオールインワンプラットフォームをお探しですか?
Apidogはこれらの要望をすべて満たし、Postmanよりもはるかに手頃な価格で代替できます!