RESTfulAPIからのページネーション応答ペイロード


83

RESTfulAPIでページネーションをサポートしたい。

私のAPIメソッドは、を介して製品のJSONリストを返す必要があります/products/index。ただし、潜在的に数千の製品があり、それらをページングしたいので、私の要求は次のようになります。

/products/index?page_number=5&page_size=20

しかし、私のJSON応答はどのように見える必要がありますか?APIコンシューマーは通常、応答でページネーションメタデータを期待しますか?それとも、一連の製品だけが必要ですか?どうして?

TwitterのAPIにはメタデータが含まれているようです:https//dev.twitter.com/docs/api/1/get/lists/members(リクエスト例を参照)。

メタデータの場合:

{
  "page_number": 5,
  "page_size": 20,
  "total_record_count": 521,
  "records": [
    {
      "id": 1,
      "name": "Widget #1"
    },
    {
      "id": 2,
      "name": "Widget #2"
    },
    {
      "id": 3,
      "name": "Widget #3"
    }
  ]
}

製品の配列のみ(メタデータなし):

[
  {
    "id": 1,
    "name": "Widget #1"
  },
  {
    "id": 2,
    "name": "Widget #2"
  },
  {
    "id": 3,
    "name": "Widget #3"
  }
]

回答:


110

ReSTful APIは主に他のシステムによって消費されるため、ページングデータを応答ヘッダーに配置します。ただし、一部のAPIコンシューマーは、応答ヘッダーに直接アクセスできない場合や、APIを介してUXを構築している場合があるため、JSON応答のメタデータを(オンデマンドで)取得する方法を提供することはプラスです。

実装には、デフォルトとして機械可読メタデータを含め、要求された場合は人間が読めるメタデータを含める必要があると思います。人間が読み取り可能なメタデータには、次のような、クエリパラメータを経由してオンデマンドで、好ましくは、必要であれば、すべての要求で返品することができinclude=metadataたりinclude_metadata=true

特定のシナリオでは、各製品のURIをレコードに含めます。これにより、APIコンシューマーは個々の製品へのリンクを簡単に作成できます。また、ページング要求の制限に従って、いくつかの合理的な期待を設定します。ページサイズのデフォルト設定を実装して文書化することは、許容できる方法です。たとえば、GitHubのAPIは、デフォルトのページサイズを最大100の30レコードに設定し、さらにAPIをクエリできる回数のレート制限を設定します。APIにデフォルトのページサイズがある場合、クエリ文字列はページインデックスを指定するだけです。

人間が読めるシナリオでは、に移動する/products?page=5&per_page=20&include=metadataと、応答は次のようになります。

{
  "_metadata": 
  {
      "page": 5,
      "per_page": 20,
      "page_count": 20,
      "total_count": 521,
      "Links": [
        {"self": "/products?page=5&per_page=20"},
        {"first": "/products?page=0&per_page=20"},
        {"previous": "/products?page=4&per_page=20"},
        {"next": "/products?page=6&per_page=20"},
        {"last": "/products?page=26&per_page=20"},
      ]
  },
  "records": [
    {
      "id": 1,
      "name": "Widget #1",
      "uri": "/products/1"
    },
    {
      "id": 2,
      "name": "Widget #2",
      "uri": "/products/2"
    },
    {
      "id": 3,
      "name": "Widget #3",
      "uri": "/products/3"
    }
  ]
}

機械可読メタデータの場合、応答にリンクヘッダーを追加します。

Link: </products?page=5&perPage=20>;rel=self,</products?page=0&perPage=20>;rel=first,</products?page=4&perPage=20>;rel=previous,</products?page=6&perPage=20>;rel=next,</products?page=26&perPage=20>;rel=last

(リンクヘッダー値はurlencodedする必要があります)

...そして、選択した場合は、カスタムtotal-count応答ヘッダーもあります。

total-count: 521

人間中心のメタデータで明らかにされた他のページングデータは、リンクヘッダーによって現在のページとページあたりの数がわかり、配列内のレコード数をすばやく取得できるため、マシン中心のメタデータには不要な場合があります。 。したがって、私はおそらく合計数のヘッダーのみを作成します。後でいつでも気が変わって、メタデータを追加できます。

余談ですが、私が/indexあなたのURIから削除したことに気付くかもしれません。一般的に受け入れられている規則は、ReSTエンドポイントにコレクションを公開させることです。持つ/indexまでわずかエンドmuddiesで。

これらは、APIを使用/作成するときに私が持っていたいもののほんの一部です。お役に立てば幸いです。


PER_PAGEは大会PAGE_SIZEに従っていません
アレクサンドロスSpyropoulos

1
"page_count": 20{"last": "/products?page=26&per_page=20"}
ジェローム・

1
1ページからxページまでのすべてのレコードをフェッチしているときに、製品の数が突然増加した場合はどうなりますか?
meV

3
@MeVは、カーソルベースのページ付けシナリオで発生するのと同じです。最後のページがいっぱいになると、合計が増加し、ページ数が増加する可能性があります。それ以上でもそれ以下でもありません。これは、このタイプのページ付けを使用するすべてのアプリで非常に一般的なシナリオです。新製品が最初のページまたは最後のページに表示されるかどうかは、使用されている並べ替えによって異なります。
Pablo Pazos 2016年

2
「ReSTfulAPIは主に他のシステムによって消費されるため、応答ヘッダーにページングデータを配置します」それは、外が晴れていると言っているようなもので、青いシャツを着ています。ヘッダーを人間が読み取れないと思う理由は何ですか?
より良いオリバー

29

RESTサービスを利用するためにいくつかのライブラリを作成したことがあるので、結果をメタデータでラップすることが方法であると私が考える理由について、クライアントの視点を示しましょう。

  • 合計数がない場合、クライアントは、存在するすべてのものをまだ受信しておらず、結果セットをページングし続ける必要があることをどのようにして知ることができますか?実行されなかったUIでは、次のページを先読みします。最悪の場合、これは実際にはそれ以上データをフェッチしなかった次/詳細リンクとして表される可能性があります。
  • 応答にメタデータを含めると、クライアントはより少ない状態を追跡できます。応答には要求の状態を再構築するために必要なメタデータ(この場合はデータセットへのカーソル)が含まれているため、REST要求を応答と一致させる必要はありません。
  • 状態が応答の一部である場合、同じデータセットに対して複数のリクエストを同時に実行でき、リクエストをたまたま到着した順序で処理できますが、必ずしもリクエストを行った順序とは限りません。

そして提案:Twitter APIのように、page_numberをストレートインデックス/カーソルに置き換える必要があります。その理由は、APIを使用すると、クライアントがリクエストごとにページサイズを設定できるためです。返されるpage_numberは、クライアントがこれまでに要求したページ数ですか、それとも最後に使用されたpage_sizeが指定されたページ数ですか(ほぼ確実に後で、そのようなあいまいさを完全に回避しないのはなぜですか)。


10
最初の箇条書きでは、次のページがない場合にrel = nextリンクを省略するのが適切な解決策でしょうか?2つ目の箇条書きでは、情報はクライアントへの応答で引き続き利用できます。情報は応答の本文ではなく、ヘッダーにあります。最後の段落で+1します。
カイルヘイズ

17

同じヘッダーを追加することをお勧めします。ヘッダにメタデータを移動するように封筒を取り除くのに役立ちますresultdataまたはrecords、応答本体が唯一の私たちに必要なデータが含まれています。ページネーションリンクも生成する場合は、Linkヘッダーを使用できます。

    HTTP/1.1 200
    Pagination-Count: 100
    Pagination-Page: 5
    Pagination-Limit: 20
    Content-Type: application/json

    [
      {
        "id": 10,
        "name": "shirt",
        "color": "red",
        "price": "$23"
      },
      {
        "id": 11,
        "name": "shirt",
        "color": "blue",
        "price": "$25"
      }
    ]

詳細については、以下を参照してください。

https://github.com/adnan-kamili/rest-api-response-format

Swaggerファイルの場合:

https://github.com/adnan-kamili/swagger-response-template


2
RFC-6648によると、「X-」プレフィックスはメタデータキーにドロップする必要があります。
レイ

1
@RayKoopaありがとう、私はgithubページを更新しましたが、この回答を更新するのを忘れました。
adnan kamili 2018年

0

バックエンドAPIの新しいプロパティを応答本文に追加するだけです。例の.netコアから:

[Authorize]
[HttpGet]
public async Task<IActionResult> GetUsers([FromQuery]UserParams userParams)
{
  var users = await _repo.GetUsers(userParams);
  var usersToReturn = _mapper.Map<IEnumerable<UserForListDto>>(users);


  // create new object and add into it total count param etc
  var UsersListResult = new
  {
    usersToReturn,
    currentPage = users.CurrentPage,
    pageSize = users.PageSize,
    totalCount = users.TotalCount,
    totalPages = users.TotalPages
  };

  return Ok(UsersListResult);
}

体の反応ではこのように見えます

{
"usersToReturn": [
    {
        "userId": 1,
        "username": "nancycaldwell@conjurica.com",
        "firstName": "Joann",
        "lastName": "Wilson",
        "city": "Armstrong",
        "phoneNumber": "+1 (893) 515-2172"
    },
    {
        "userId": 2,
        "username": "zelmasheppard@conjurica.com",
        "firstName": "Booth",
        "lastName": "Drake",
        "city": "Franks",
        "phoneNumber": "+1 (800) 493-2168"
    }
],
// metadata to pars in client side
"currentPage": 1,
"pageSize": 2,
"totalCount": 87,
"totalPages": 44

}


-3

一般的に、私は簡単な方法で作成します。たとえば、これらのパラメーターを使用して「localhost / api / method /:lastIdObtained /:countDateToReturn」などのrestAPIエンドポイントを作成します。これは、簡単なリクエストで実行できます。サービスで、例えば。。ネット

jsonData function(lastIdObtained,countDatetoReturn){
'... write your code as you wish..'
and into select query make a filter
select top countDatetoreturn tt.id,tt.desc
 from tbANyThing tt
where id > lastIdObtained
order by id
}

Ionicでは、下から上にスクロールするとゼロ値を渡し、答えが得られたら最後に取得したIDの値を設定し、上から下にスライドすると最後に取得した登録IDを渡します

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.