Skip to content
ADP
API Design PrincipleBETA

[ADP-311] Filtering

TIP

You MAY use a simple ?k=v&a=b approach if logical filtering is not required. However, if your use case necessitates logical operations, it is RECOMMENDED to consider implementing this approach instead of devising a new mechanism. Alternatively, if you have a better idea, you may propose it to the API Design Principle owner for review, as there is no universally accepted 'filtering' standard.

Overview

Filtering is a common feature in RESTful APIs, allowing clients to retrieve only the data that meets specific criteria. This guideline outlines a standardized approach for implementing filtering in RESTful APIs, focusing on nested attributes and other common methods.

Guidance

  • SHOULD document filtering methods if your API supports filtering.
  • SHOULD use the common query parameter filter for resource filtering.

We propose two different filtering approaches: RHS (Right-Hand Side) and LHS (Left-Hand Side).

RHS is suitable for simple queries, while LHS is beneficial for complex logical operations.

RHS Filtering

  • SHOULD support complex logic combinations with RHS query parameters

    http
    GET /stores?filter=eq(name,John) HTTP/1.1
  • SHOULD support nested filters

    http
    GET /stores?filter=eq(user(name),John) HTTP/1.1
  • MAY support logical operations

    http
    GET /stores?filter=gt(registeredAt,2024-07-13T00:00:00) HTTP/1.1

    Supported operations:

    • eq({property},{value}): equal to the given value
    • ne({property},{value}): not equal to the given value
    • gt({property},{value}): greater than the given value
    • ge({property},{value}): greater than or equal to the given value
    • lt({property},{value}): less than the given value
    • le({property},{value}): less than or equal to the given value
    • in({property},{value1},{value2},...): contains at least one of the listed values
    • like({property},{value}): contains values similar to the listed expressions
    • exists({property}): the given path exists
    • and({query1},{query2},...): logical AND
    • or({query1},{query2},...): logical OR
    • not({query}): logical NOT

LHS Filtering

  • SHOULD support simple LHS filtering using the common filter query parameter

    http
    GET /users?filter.name=John HTTP/1.1
  • SHOULD support nested filters

    http
    GET /users?filter.address.region=eu HTTP/1.1
  • SHOULD support value OR using ,

    http
    GET /users?filter.role=MANAGER,SUPERVISOR
  • MAY support logical operations

    http
    GET /users?filter.createdAt:gt=2024-07-13T00:00:00 HTTP/1.1

    Supported operations:

    • filter.{property}:eq={value}: equal to (can be omitted)
    • filter.{property}:ne={value}: not equal to
    • filter.{property}:gt={value}: greater than
    • filter.{property}:ge={value}: greater than or equal to
    • filter.{property}:lt={value}: less than
    • filter.{property}:le={value}: less than or equal to
  • MAY support comma-separated values for OR operations

    http
    GET /users?filter.name=John,Jason
  • MAY add extra namespaces to meet different requirements, surrounding the property part. If implemented, MUST document it thoroughly.

    http
    GET /licenses?filter=eq(features/featurelist,"nightwatch")

Design Considerations

Why not use property-only design?

A possible design for result filtering is using only the field name, such as:

http
GET /emaps?region=us&category=self-defined

The main issue with this approach is potential conflicts with common query parameters. As you implement more custom query parameters, it's more likely to collide.

LHS/RHS

LHS (Left-Hand Side) means placing the logic on the left-hand side of the = sign. RHS is for the right-hand side.

  • Left-Hand Side brackets

    http
    filter[key1][gt]=x
  • Right-Hand Side brackets

    http
    filter[key1]=[gt]x
  • Left-Hand Side colon

    http
    filter.key:gte=y
  • Right-Hand Side colon

    http
    filter.key=gte:x

Example with OpenAPI

RHS Filtering Example

yaml
openapi: 3.1.0
info:
  title: Stores API
  version: 1.0.0
paths:
  /stores:
    get:
      summary: Get stores list
      parameters:
        - in: query
          name: filter
          schema:
            type: string
          example: eq(name,John)
          description: RHS filtering parameter
      responses:
        '200':
          description: Successful response
          content:
            application/json:    
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Store'
components:
  schemas:
    Store:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        registeredAt:
          type: string
          format: date-time

LHS Filtering Example

yaml
openapi: 3.1.0
info:
  title: Users API
  version: 1.0.0
paths:
  /users:
    get:
      summary: Get users list
      parameters:
        - in: query
          name: filter.name
          schema:
            type: string
          example: John
          description: Filter by name
        - in: query
          name: filter.role
          schema:
            type: string
          example: MANAGER,SUPERVISOR
          description: Filter by role (supports multiple values)
        - in: query
          name: filter.createdAt:gt
          schema:
            type: string
            format: date-time
          example: '2024-07-13T00:00:00'
          description: Filter by creation time (greater than)
      responses:
        '200':
          description: Successful response
          content:
            application/json:    
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'
components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        role:
          type: string
        createdAt:
          type: string
          format: date-time

Reference

Design Reference

Changelog

  • 2024.09.20 Add tips for common cases.