Data grid support - waterdog-oss/ktor-template GitHub Wiki

Abstract:

Definition of the format for pagination, sorting and filtering returned by REST APIs in the context of Waterdog projects.

Introduction/motivation:

Data grids are a common requirement for various APIs. We should adopt a "standard" format that covers the most common use cases (pagination, sorting, filtering, etc) that plays well with the front-end libraries we usually tend to use.

Requirements:

  • For pagination requests we should be able to limit size and the current page of the results. The response should indicate the total of available elements so that front-end libraries can present the pagination controls.
  • For sorting requests we should be able to select one or more fields to order the results and indicate if in ascending or descending order.
  • For filter requests we should be able to select one or more fields and indicate the value we want to use as a filter.

References:

Proposed query filter format:

page[number]=0&page[size]=10: URL query filters to select current page number and size of results.

sort[id]=desc&sort[brand]=asc: URL query filter to select the order of the results and its order, e.g. in this example the results are first ordered in descending order by id and then in ascending order by brand.

filter[brand]=brand1,brand2&filter[model]=model1: URL query filter to filter fields by the provided values.

Examples:

a) Pagination:

/resource?page[number]=0&page[size]=2
{
  "meta": {
    "page": 0,
    "size": 2,
    "totalElements": 5,
    "totalPages": 3,
    "firstPage": 0,
    "lastPage": 2,
    "first": true,
    "last": false,
    "nextPage": 1
  },
  "data": [
    {
      "id": 1,
      "brand": "brand1",
      "model": "model1"
    },
    {
      "id": 2,
      "brand": "brand2",
      "model": "model2"
    }
  ],
  "links": {
    "self": "/resource?page[number]=0&page[size]=2",
    "first": "/resource?page[number]=0&page[size]=2",
    "next": "/resource?page[number]=1&page[size]=2",
    "last": "/resource?page[number]=2&page[size]=2"
  }
}

b) Sorting:

/resource?sort[id]=desc
{
  "meta": {
    "page": 0,
    "size": 10,
    "totalElements": 5,
    "totalPages": 1,
    "firstPage": 0,
    "lastPage": 0,
    "first": true,
    "last": true
  },
  "data": [
    {
      "id": 5,
      "brand": "brand5",
      "model": "model5"
    },
    {
      "id": 4,
      "brand": "brand4",
      "model": "model4"
    },
    {
      "id": 3,
      "brand": "brand3",
      "model": "model3"
    },
    {
      "id": 2,
      "brand": "brand2",
      "model": "model2"
    },
    {
      "id": 1,
      "brand": "brand1",
      "model": "model1"
    }
  ],
  "links": {
    "self": "/resource?page[number]=0&page[size]=10&sort[id]=desc",
    "first": "/resource?page[number]=0&page[size]=10&sort[id]=desc",
    "last": "/resource?page[number]=0&page[size]=10&sort[id]=desc"
  }
}

c) Filtering:

/resource?filter[id]=1
{
  "meta": {
    "page": 0,
    "size": 10,
    "totalElements": 1,
    "totalPages": 1,
    "firstPage": 0,
    "lastPage": 0,
    "first": true,
    "last": true
  },
  "data": [
    {
      "id": 1,
      "brand": "brand1",
      "model": "model1"
    }
  ],
  "links": {
    "self": "/resource?page[number]=0&page[size]=10&filter[id]=1",
    "first": "/resource?page[number]=0&page[size]=10&filter[id]=1",
    "last": "/resource?page[number]=0&page[size]=10&filter[id]=1"
  }
}