• English
  • Input design for queries and mutations

    This standard defines the input structure for all queries and mutations in our GraphQL APIs: a single input object with consistent filtering, search, sorting, and pagination.

    The problem

    Without a standard structure, each query invents its own way of passing parameters and the API becomes inconsistent, difficult to use, and hard to maintain as it grows. The typical symptoms:

    • Inconsistent parameter passing conventions across queries.
    • Filtering and sorting that don't scale to complex cases.
    • Poor discoverability of the available options.
    • Difficult version management.
    • An input structure that is hard to document.

    The solution: a standardized input object

    All queries and mutations wrap their parameters in a single input argument with a consistent structure:

    1. Root input object: all parameters are passed via a single input argument.
    2. Identifier fields: resource identifiers (for example organizationId, userId) are direct properties of the input.
    3. filter object: nested object for filtering, with consistent operators.
    4. search object: structured search parameters.
    5. sort array: standard sorting mechanism.
    6. pagination object: consistent pagination controls.

    Mandatory requirements

    Every new query and mutation schema design meets these requirements:

    1. Single input: always use a single parameter named input that accepts an input object.
    2. Consistent naming: every input type ends with the Input suffix (for example, ATSListJobApplicantInput).
    3. Standard operators: use consistent operator suffixes for filtering:
      • _eq, _neq for equality
      • _gt, _gte, _lt, _lte for comparisons
      • _in, _nin for array inclusion or exclusion
    4. Pagination: every list query supports standard pagination.
    5. Sorting: every list query supports sorting by its relevant fields.
    6. Search: where applicable, implement the standard search pattern.
    7. Identifier fields: entity identifiers always go as top-level fields of the input object.
    8. Documentation: every input field carries its description.

    Schema definition

    # Base input structure for all queries/mutations
    input BaseQueryInput {
      # Optional filter object
      filter: FilterInput
      # Optional search parameters
      search: SearchInput
      # Optional sorting parameters
      sort: [SortInput!]
      # Optional pagination parameters
      pagination: CursorPaginationInput | OffsetPaginationInput
    }
    
    # Common filtering patterns
    input FilterInput {
      # Fields vary based on entity but operator patterns remain consistent
      # Examples of common operators:
      # _eq: Equals
      # _neq: Not equals
      # _gt: Greater than
      # _gte: Greater than or equal
      # _lt: Less than
      # _lte: Less than or equal
      # _in: Included in array
      # _nin: Not included in array
    }
    
    # Search functionality
    input SearchInput {
      # Search query string
      query: String!
      # Optional fields to search within
      fields: [String!]
    }
    
    # Sorting control
    input SortInput {
      # Field to sort by
      field: String!
      # Sort direction
      direction: SortDirection!
    }
    
    # Sort direction enum
    enum SortDirection {
      ASC
      DESC
    }
    
    # Pagination control
    input CursorPaginationInput {
      after: String
      first: Int
    }
    
    input OffsetPaginationInput {
      # Page number (1-based)
      page: Int
      # Items per page
      pageSize: Int
    }
    
    # Entity-specific input extending base input
    input ATSListJobApplicantInput {
      # Required organization ID
      organizationId: ID!
      # Optional job ID
      jobId: ID
      # Filtering options specific to job applicants
      filter: JobApplicantFilterInput
      # Standard search, sort, pagination
      search: SearchInput
      sort: [SortInput!]
      pagination: CursorPaginationInput | OffsetPaginationInput
    }
    
    # Entity-specific filter
    input JobApplicantFilterInput {
      # Applicant status filtering
      status_in: [ApplicantStatus!]
      # Date range filtering
      appliedDate_gte: DateTime
      appliedDate_lte: DateTime
      # Custom filters
      customField_eq: String
    }

    Usage examples

    List job applicants

    query AtsAdminApplicantListPageGetApplicantList($input: ATSListJobApplicantInput!) {
      ats_recruitment {
        list_job_applicant_admin(input: $input) {
          id
          name
          email
          status
          appliedDate
        }
      }
    }

    With these variables:

    {
      "input": {
        "centralized_organization_id": "1",
        "filter": {
          "status_in": ["APPLIED", "SCREENING"],
          "applied_date_gte": "2025-01-01T00:00:00Z"
        },
        "search": {
          "query": "developer",
          "fields": ["name", "resume"]
        },
        "sort": [{ "field": "applied_date", "direction": "DESC" }],
        "pagination": {
          "page": 1,
          "pageSize": 20
        }
      }
    }
    query ProductSearch($input: ProductSearchInput!) {
      products {
        search(input: $input) {
          id
          title
          price
          category
          brand
        }
      }
    }

    With these variables:

    {
      "input": {
        "centralized_organization_id": "1",
        "filter": {
          "category": "ELECTRONICS",
          "price_gte": 1000,
          "brand_in": ["BrandA", "BrandB"]
        },
        "search": {
          "query": "laptop",
          "fields": ["title", "description"]
        },
        "sort": [{ "field": "price", "direction": "ASC" }],
        "pagination": {
          "page": 1,
          "pageSize": 50
        }
      }
    }

    Advantages and disadvantages

    The pattern trades verbosity for consistency.

    Advantages:

    • Consistency: uniform structure across all queries and mutations.
    • Scalability: easily extensible with new filtering options without breaking changes.
    • Discoverability: clear patterns make it easier to guess which options exist.
    • Maintainability: consistent patterns simplify API maintenance.
    • Documentation: the expected input format is easier to document and understand.
    • Evolution: the API evolves gradually without breaking clients.
    • Less boilerplate: pagination and sorting are standardized.

    Disadvantages:

    • Verbosity: more verbose than simple parameter passing for simple queries.
    • Learning curve: new team members need to learn the pattern conventions.
    • Implementation complexity: the backend needs more code to handle the flexible structure.
    • Performance: generic filtering can be less performant than a specialized query.
    • Schema size: the GraphQL schema documentation can grow.