Skip to main content

Best Practices

This chapter provides best practices for writing efficient and maintainable GraphQL queries and mutations.

Pagination

Always use pagination for queries that may return large result sets.

Cursor-based pagination

query getEnergyMeters($first: Int!, $after: String) {
runtime {
industryEnergyEnergyMeter(first: $first, after: $after) {
pageInfo {
endCursor
hasNextPage
}
totalCount
items {
rtId
name
}
}
}
}

Pagination strategy

  1. Start with reasonable page sizes: 20-50 items for UI lists, up to 100 for background processing
  2. Use hasNextPage: Check before making additional requests
  3. Preserve cursors: Store endCursor for "load more" functionality
  4. Consider totalCount: Use sparingly as it may require a full count query

Query only needed fields

Request only the fields you actually need. This reduces response size and improves performance.

# Good - only request needed fields
query {
runtime {
industryEnergyEnergyMeter(first: 10) {
items {
rtId
name
state
}
}
}
}

# Avoid - requesting all fields when only a few are needed
query {
runtime {
industryEnergyEnergyMeter(first: 10) {
items {
rtId
ckTypeId
name
description
voltage
ampere
power
state
# ... many more fields
}
}
}
}

Use variables

Always use GraphQL variables instead of string interpolation. This improves security, caching, and readability.

# Good - using variables
query getEnergyMeter($rtId: OctoObjectId!) {
runtime {
industryEnergyEnergyMeter(rtId: $rtId) {
items {
rtId
name
}
}
}
}

# Avoid - hardcoded values
query {
runtime {
industryEnergyEnergyMeter(rtId: "65dc6d24cc529cdc46c84fcc") {
items {
rtId
name
}
}
}
}

Aliases

Use aliases when you need to query the same field with different arguments in a single request:

query {
runtime {
activeMeters: industryEnergyEnergyMeter(
fieldFilter: [{ attributePath: "state", operator: EQUALS, comparisonValue: "ON" }]
) {
totalCount
}
inactiveMeters: industryEnergyEnergyMeter(
fieldFilter: [{ attributePath: "state", operator: EQUALS, comparisonValue: "OFF" }]
) {
totalCount
}
}
}

Result:

{
"data": {
"runtime": {
"activeMeters": { "totalCount": 15 },
"inactiveMeters": { "totalCount": 8 }
}
}
}

Fragments

Use fragments to reuse field selections across multiple queries:

fragment EnergyMeterFields on IndustryEnergyEnergyMeter {
rtId
ckTypeId
name
voltage
state
}

query getEnergyMeters {
runtime {
industryEnergyEnergyMeter(first: 10) {
items {
...EnergyMeterFields
}
}
}
}

query getEnergyMeterById($rtId: OctoObjectId!) {
runtime {
industryEnergyEnergyMeter(rtId: $rtId) {
items {
...EnergyMeterFields
description
ampere
power
}
}
}
}

Batch operations

When creating or updating multiple entities, use batch operations instead of multiple requests:

# Good - single request with multiple entities
mutation createMultipleMeters($entities: [IndustryEnergyEnergyMeterInput]!) {
runtime {
industryEnergyEnergyMeters {
create(entities: $entities) {
rtId
name
}
}
}
}

# Avoid - multiple separate requests
# Request 1: create entity A
# Request 2: create entity B
# Request 3: create entity C

Filter optimization

Use specific filters

More specific filters result in faster queries:

# Good - specific filter
query {
runtime {
industryEnergyEnergyMeter(
fieldFilter: [
{ attributePath: "state", operator: EQUALS, comparisonValue: "ON" },
{ attributePath: "voltage", operator: GREATER_THAN, comparisonValue: 220 }
]
) {
items { rtId name }
}
}
}

# Less efficient - broad filter with client-side filtering
query {
runtime {
industryEnergyEnergyMeter {
items { rtId name state voltage }
}
}
}
# Then filter in code...

Use rtId when available

Direct rtId lookups are the fastest:

# Fastest - direct ID lookup
query {
runtime {
industryEnergyEnergyMeter(rtId: "65dc6d24cc529cdc46c84fcc") {
items { rtId name }
}
}
}

Avoid N+1 queries

Use associations within a single query instead of making separate requests:

# Good - single query with associations
query {
runtime {
systemCommunicationDataPipeline(first: 10) {
items {
rtId
name
children {
systemCommunicationPipeline {
items {
rtId
name
}
}
}
}
}
}
}

# Avoid - N+1 pattern
# Query 1: Get all pipelines
# Query 2-N: For each pipeline, get children

Naming conventions

Use descriptive names for operations:

# Good - descriptive operation names
query getActiveEnergyMetersByLocation { ... }
mutation updateEnergyMeterState { ... }

# Avoid - generic or missing names
query { ... }
mutation doUpdate { ... }

Error handling

Always handle errors appropriately. See Error Handling for details.

const response = await client.query({ query: GET_METERS });

if (response.errors) {
// Handle errors
}

if (response.data) {
// Process data
}

Summary

PracticeBenefit
Use paginationPrevents large response payloads
Query only needed fieldsReduces response size
Use variablesSecurity, caching, readability
Use aliasesMultiple queries in one request
Use fragmentsDRY, maintainable field selections
Batch operationsFewer network requests
Specific filtersFaster query execution
Avoid N+1Fewer database queries