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
- Start with reasonable page sizes: 20-50 items for UI lists, up to 100 for background processing
- Use
hasNextPage: Check before making additional requests - Preserve cursors: Store
endCursorfor "load more" functionality - 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
| Practice | Benefit |
|---|---|
| Use pagination | Prevents large response payloads |
| Query only needed fields | Reduces response size |
| Use variables | Security, caching, readability |
| Use aliases | Multiple queries in one request |
| Use fragments | DRY, maintainable field selections |
| Batch operations | Fewer network requests |
| Specific filters | Faster query execution |
| Avoid N+1 | Fewer database queries |