Edit Page

Custom Resolvers in RESTHeart GraphQL

Custom resolvers provide advanced flexibility in how GraphQL fields are resolved in RESTHeart. This guide covers resolver types, implementation patterns, and best practices.

Understanding Resolvers

In RESTHeart GraphQL, resolvers are special functions that determine how to fetch or compute data for a specific field. While basic field mappings handle most cases, custom resolvers enable more complex scenarios.

Resolver Types

  1. Type Resolvers

    • Determine concrete types for interfaces and unions

    • Handle polymorphic data structures

    • Map document fields to GraphQL types

  2. Field Resolvers

    • Compute field values dynamically

    • Transform data during resolution

    • Handle complex data relationships

Type Resolvers

Interface Type Resolution

Type resolvers for interfaces determine which concrete type a document represents:

{
    "mappings": {
        "Content": {
            "$typeResolver": {
                "Article": "field-exists(wordCount)",
                "Video": "field-exists(duration)",
                "Podcast": "field-exists(audioUrl)"
            }
        }
    }
}

Union Type Resolution

Similar to interfaces, but for union types:

{
    "mappings": {
        "SearchResult": {
            "$typeResolver": {
                "User": "field-exists(email)",
                "Product": "field-exists(price)",
                "Article": "field-exists(content)"
            }
        }
    }
}

Type Resolution Rules

  1. Evaluation Order

    • Rules are evaluated in order

    • First matching rule determines the type

    • Include a default case when possible

  2. Available Functions

    • field-exists(fieldName): Checks if a field exists

    • field-equals(fieldName, value): Compares field value

    • field-type(fieldName, type): Checks field data type

{
    "mappings": {
        "Node": {
            "$typeResolver": {
                "Document": "field-type(content, string)",
                "Image": "field-equals(type, 'image')",
                "Default": "true"
            }
        }
    }
}

Field Resolvers

Basic Field Resolution

Transform field values during resolution:

{
    "mappings": {
        "User": {
            "fullName": {
                "$resolver": "concat",
                "fields": ["firstName", "lastName"],
                "separator": " "
            }
        }
    }
}

Computed Fields

Calculate values based on other fields:

{
    "mappings": {
        "Product": {
            "discountedPrice": {
                "$resolver": "multiply",
                "field": "price",
                "factor": 0.9
            },
            "profit": {
                "$resolver": "subtract",
                "field1": "price",
                "field2": "cost"
            }
        }
    }
}

Conditional Resolution

Resolve fields based on conditions:

{
    "mappings": {
        "Order": {
            "status": {
                "$resolver": "conditional",
                "conditions": [
                    {
                        "if": "field-equals(paid, true)",
                        "then": "PAID"
                    },
                    {
                        "if": "field-exists(cancelledAt)",
                        "then": "CANCELLED"
                    },
                    {
                        "else": "PENDING"
                    }
                ]
            }
        }
    }
}

Built-in Resolvers

RESTHeart provides several built-in resolvers:

Resolver Description Example Use Case

concat

Concatenates multiple fields

Combining name parts

multiply

Multiplies a field by a factor

Calculating discounts

divide

Divides a field by a divisor

Computing rates

add

Adds multiple fields

Summing values

subtract

Subtracts fields

Computing differences

conditional

Conditional field resolution

Status determination

format

Formats field values

Date/number formatting

Custom JavaScript Resolvers

For more complex resolution logic, you can implement custom JavaScript resolvers:

{
    "mappings": {
        "Order": {
            "total": {
                "$resolver": "javascript",
                "code": "
                    function resolve(doc) {
                        return doc.items.reduce((sum, item) =>
                            sum + (item.price * item.quantity), 0);
                    }
                "
            }
        }
    }
}

JavaScript Resolver Context

JavaScript resolvers have access to:

  1. Document Context

    • Current document as first argument

    • Parent document fields

    • Query arguments

  2. Helper Functions

    • MongoDB ObjectId creation

    • Date manipulation

    • String formatting

Security Considerations

When using JavaScript resolvers:

  1. Input Validation

    • Validate all inputs

    • Sanitize user data

    • Handle edge cases

  2. Resource Usage

    • Keep functions simple

    • Avoid infinite loops

    • Limit recursion depth

  3. Error Handling

    • Catch exceptions

    • Provide fallback values

    • Log errors appropriately

Performance Optimization

1. Caching

Cache computed values when possible:

{
    "mappings": {
        "Product": {
            "stats": {
                "$resolver": "javascript",
                "code": "...",
                "cache": {
                    "enabled": true,
                    "ttl": 300
                }
            }
        }
    }
}

2. Batching

Group related resolutions:

{
    "mappings": {
        "Order": {
            "items": {
                "$resolver": "batchLoad",
                "maxBatchSize": 100,
                "batchBy": "orderId"
            }
        }
    }
}

Best Practices

  1. Keep Resolvers Simple

    • One responsibility per resolver

    • Clear, maintainable logic

    • Document complex resolvers

  2. Error Handling

    • Provide fallback values

    • Validate inputs

    • Log errors appropriately

  3. Performance

    • Use caching when possible

    • Batch related operations

    • Monitor resolver performance

  4. Security

    • Validate all inputs

    • Sanitize user data

    • Follow least privilege principle

Next Steps