REST Lens
← Back to All Rules
Rule 71

Query Filter Collection

Category: Query Parameters

Description

Collection GET endpoints must support filtering query parameters beyond pagination to allow clients to retrieve specific subsets of data based on criteria.

What It Checks

This rule validates that GET operations on collections (endpoints returning arrays) include query parameters for filtering results. It checks:

  • GET operations that return arrays or collections
  • Collection endpoints identified by plural nouns (e.g., /users, /products)
  • The presence of query parameters beyond standard pagination parameters
  • Excludes singleton resource endpoints (those with path parameters like {id})
  • Excludes verb endpoints (e.g., /users/search)

The rule automatically filters out common pagination parameters before checking for filters:

  • Page number style: page, page_number, size, per_page
  • Limit/offset style: limit, page_size, offset, page_start_index
  • Cursor style: cursor
  • Time-based style: since, until

Why It Matters

Supporting filter parameters on collections is essential for building practical, usable APIs:

  • Efficiency: Clients can request only the data they need, reducing payload size and processing time
  • Performance: Filtering at the server level is more efficient than retrieving all data and filtering client-side
  • Usability: Enables clients to build meaningful features like search, status filtering, and data exploration
  • Scalability: Reduces network bandwidth and server load by returning smaller, targeted datasets
  • User Experience: Allows building responsive interfaces with quick, filtered data retrieval

Examples

Good ✅

User Collection with Status Filter

paths:
  /users:
    get:
      summary: List users with filtering
      parameters:
        - name: page
          in: query
          schema:
            type: integer
        - name: per_page
          in: query
          schema:
            type: integer
        - name: status
          in: query
          description: Filter by user status
          schema:
            type: string
            enum: [active, inactive, pending]
        - name: role
          in: query
          description: Filter by user role
          schema:
            type: string
      responses:
        '200':
          description: Filtered list of users
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'

Product Collection with Multiple Filters

paths:
  /products:
    get:
      summary: List products
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
        - name: offset
          in: query
          schema:
            type: integer
        - name: category
          in: query
          description: Filter by product category
          schema:
            type: string
        - name: min_price
          in: query
          description: Minimum price filter
          schema:
            type: number
        - name: max_price
          in: query
          description: Maximum price filter
          schema:
            type: number
        - name: in_stock
          in: query
          description: Filter to only in-stock items
          schema:
            type: boolean
      responses:
        '200':
          description: Filtered products

Order Collection with Date Range Filter

paths:
  /orders:
    get:
      summary: List orders
      parameters:
        - name: cursor
          in: query
          schema:
            type: string
        - name: status
          in: query
          description: Filter by order status
          schema:
            type: string
            enum: [pending, processing, shipped, delivered, cancelled]
        - name: customer_id
          in: query
          description: Filter by customer ID
          schema:
            type: string
        - name: created_after
          in: query
          description: Filter orders created after this date
          schema:
            type: string
            format: date-time
        - name: created_before
          in: query
          description: Filter orders created before this date
          schema:
            type: string
            format: date-time
      responses:
        '200':
          description: Filtered orders

Bad ❌

Collection with Only Pagination (No Filters)

paths:
  /users:
    get:
      summary: List users
      parameters:
        - name: page
          in: query
          schema:
            type: integer
        - name: per_page
          in: query
          schema:
            type: integer
        # No filter parameters - always returns all users for the page
      responses:
        '200':
          description: Paginated users
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'

Products Collection Without Filters

paths:
  /products:
    get:
      summary: List all products
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
        - name: offset
          in: query
          schema:
            type: integer
        # Missing filter parameters like category, price range, etc.
      responses:
        '200':
          description: All products

Orders with No Query Parameters at All

paths:
  /orders:
    get:
      summary: Get all orders
      # No parameters at all - neither pagination nor filters
      responses:
        '200':
          description: Complete list of orders
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Order'

Acceptable Scenarios

The rule allows certain endpoints to skip filter requirements:

  1. Singleton Resources: Endpoints that retrieve a single item

    /users/{userId}  # Single user retrieval
    
  2. Verb Endpoints: Action-oriented endpoints

    /users/search  # Search is a verb, different pattern
    
  3. Non-Collection Responses: GET endpoints that don't return arrays

    /dashboard/summary  # Returns single summary object
    

Configuration

This rule has no configuration options. It is either enabled or disabled. The list of acceptable pagination parameters is hardcoded based on industry-standard pagination styles.

Common Filter Patterns

Status Filtering

- name: status
  in: query
  schema:
    type: string
    enum: [active, inactive, pending]

Relationship Filtering

- name: user_id
  in: query
  description: Filter by associated user
  schema:
    type: string

- name: organization_id
  in: query
  description: Filter by organization
  schema:
    type: string

Range Filtering

- name: min_price
  in: query
  schema:
    type: number

- name: max_price
  in: query
  schema:
    type: number

- name: created_after
  in: query
  schema:
    type: string
    format: date-time

- name: created_before
  in: query
  schema:
    type: string
    format: date-time

Boolean Flags

- name: is_featured
  in: query
  schema:
    type: boolean

- name: in_stock
  in: query
  schema:
    type: boolean

Multi-Value Filtering

- name: categories
  in: query
  description: Filter by multiple categories
  schema:
    type: array
    items:
      type: string
  style: form
  explode: true

How to Fix

1. Identify Filterable Attributes

Analyze your resource model and identify which fields make sense for filtering:

// User resource
{
  id: "123",
  name: "John Doe",
  email: "[email protected]",
  status: "active",      // Good filter candidate
  role: "admin",         // Good filter candidate
  created_at: "2024-01-15",  // Good for date range filtering
  department_id: "dept-5"    // Good for relationship filtering
}

2. Add Filter Parameters to Your Spec

Add query parameters for each filterable field:

# Before
/users:
  get:
    summary: List users
    parameters:
      - name: page
        in: query
        schema:
          type: integer

# After
/users:
  get:
    summary: List users with filtering
    parameters:
      - name: page
        in: query
        schema:
          type: integer
      - name: status
        in: query
        description: Filter by user status
        schema:
          type: string
          enum: [active, inactive, pending]
      - name: role
        in: query
        description: Filter by user role
        schema:
          type: string
      - name: department_id
        in: query
        description: Filter by department
        schema:
          type: string
      - name: created_after
        in: query
        description: Filter users created after this date
        schema:
          type: string
          format: date-time

3. Implement Server-Side Filtering

Ensure your backend actually implements the filtering logic:

// Example Express.js implementation
app.get('/users', async (req, res) => {
  const { page = 1, per_page = 20, status, role, department_id, created_after } = req.query;

  let query = db.select().from('users');

  // Apply filters
  if (status) {
    query = query.where('status', status);
  }
  if (role) {
    query = query.where('role', role);
  }
  if (department_id) {
    query = query.where('department_id', department_id);
  }
  if (created_after) {
    query = query.where('created_at', '>=', created_after);
  }

  // Apply pagination
  const offset = (page - 1) * per_page;
  query = query.limit(per_page).offset(offset);

  const users = await query;
  res.json({ data: users, page, per_page });
});

4. Document Filter Behavior

Clearly document how filters combine:

/products:
  get:
    summary: List products
    description: |
      Retrieve a filtered list of products. All filters are applied with AND logic.
      For example, `?category=electronics&in_stock=true` returns only in-stock electronics.
    parameters:
      - name: category
        in: query
        description: Filter by product category
        schema:
          type: string
      - name: in_stock
        in: query
        description: Filter to only in-stock items
        schema:
          type: boolean

5. Consider Common Use Cases

Add filters that support your API's primary use cases:

  • E-commerce: price range, category, availability, brand
  • User management: status, role, department, registration date
  • Content: author, publication date, category, tags
  • Orders: status, customer, date range, total amount range

Related Rules

  • Rule 48: Pagination - Ensures collections support pagination (this rule excludes those params)
  • Rule 23: Query Fields GET - Supports field selection for partial responses
  • Rule 1: Plural Collections - Ensures collection endpoints use plural nouns

Best Practices

  1. Combine Filters with AND Logic: When multiple filters are provided, apply them all (AND logic is most intuitive)

  2. Support Common Patterns: Include filters for status, relationships, date ranges, and boolean flags

  3. Use Enums for Fixed Values: When filter values are limited, use enums to document valid options

  4. Document Default Behavior: Clearly state what happens when no filters are provided

  5. Validate Filter Values: Ensure filter parameters are validated server-side to prevent injection attacks

  6. Consider Performance: Add database indexes on frequently filtered fields

  7. Return Empty Arrays: When filters match no results, return an empty array (not an error)

  8. Case Sensitivity: Document whether string filters are case-sensitive

  9. Partial Matching: Decide whether string filters require exact matches or support partial matching (document this clearly)