Skip to content

GraphQL Module

The graphql module enables execution of GraphQL queries and mutations against the application's core GraphQL schema. It automatically uses the current actor's permissions for authorization and provides full access to the schema's capabilities.

Functions

graphql.query(document, variables)

Executes a GraphQL query or mutation with full schema access.

Parameters:

  • document (string): GraphQL query/mutation document
  • variables (table): Variables to pass to the GraphQL query (optional, defaults to empty table)

Returns:

  • [success, result] where:
    • success (boolean): true if query executed, false on error
    • result (table): GraphQL response containing:
      • data (table): Query results if successful
      • errors (array): Error details if any occurred

Basic Query Example:

lua
-- Simple query without variables
local success, response = graphql.query([[
    query {
        listUsers {
            id
            name
            email
            active
        }
    }
]])

if success and response.data then
    for _, user in ipairs(response.data.listUsers) do
        print(user.name .. " <" .. user.email .. ">")
    end
end

-- Query with variables
local success, response = graphql.query([[
    query($userId: ID!) {
        getUser(id: $userId) {
            id
            name
            email
            roles {
                name
                permissions
            }
        }
    }
]], { userId = "user-123" })

if success and response.data then
    local user = response.data.getUser
    print("User: " .. user.name)
    for _, role in ipairs(user.roles) do
        print("  Role: " .. role.name)
    end
end

Mutations

lua
-- Create mutation
local success, response = graphql.query([[
    mutation($input: CreateUserInput!) {
        createUser(input: $input) {
            id
            name
            email
            createdAt
        }
    }
]], {
    input = {
        name = "Jane Doe",
        email = "jane@example.com",
        role = "USER"
    }
})

if success and response.data then
    local new_user = response.data.createUser
    print("Created user: " .. new_user.id)
elseif response.errors then
    for _, error in ipairs(response.errors) do
        print("Error: " .. error.message)
    end
end

-- Update mutation
local success, response = graphql.query([[
    mutation($id: ID!, $updates: UpdateUserInput!) {
        updateUser(id: $id, input: $updates) {
            id
            name
            email
            updatedAt
        }
    }
]], {
    id = "user-123",
    updates = {
        name = "Jane Smith",
        active = true
    }
})

-- Delete mutation
local success, response = graphql.query([[
    mutation($id: ID!) {
        deleteUser(id: $id) {
            success
            message
        }
    }
]], { id = "user-456" })

Complex Queries

Nested Relationships

lua
local success, response = graphql.query([[
    query($orderId: ID!) {
        getOrder(id: $orderId) {
            id
            status
            total
            customer {
                id
                name
                email
                address {
                    street
                    city
                    country
                }
            }
            items {
                id
                product {
                    name
                    sku
                    price
                }
                quantity
                subtotal
            }
            shipment {
                trackingNumber
                carrier
                estimatedDelivery
            }
        }
    }
]], { orderId = order_id })

if success and response.data then
    local order = response.data.getOrder
    print("Order " .. order.id .. " for " .. order.customer.name)
    print("Total: $" .. order.total)

    for _, item in ipairs(order.items) do
        print("  - " .. item.product.name .. " x" .. item.quantity)
    end
end

Pagination

lua
function fetch_all_users(page_size)
    local all_users = {}
    local cursor = nil
    local has_more = true

    while has_more do
        local success, response = graphql.query([[
            query($first: Int!, $after: String) {
                users(first: $first, after: $after) {
                    edges {
                        node {
                            id
                            name
                            email
                        }
                        cursor
                    }
                    pageInfo {
                        hasNextPage
                        endCursor
                    }
                }
            }
        ]], {
            first = page_size or 50,
            after = cursor
        })

        if success and response.data then
            for _, edge in ipairs(response.data.users.edges) do
                table.insert(all_users, edge.node)
            end

            has_more = response.data.users.pageInfo.hasNextPage
            cursor = response.data.users.pageInfo.endCursor
        else
            print("Failed to fetch users page")
            break
        end
    end

    return all_users
end

-- Fetch all users in batches of 100
local users = fetch_all_users(100)
print("Total users fetched: " .. #users)

Fragments and Reusable Queries

lua
-- Define reusable fragments
local USER_FIELDS = [[
    fragment UserFields on User {
        id
        name
        email
        createdAt
        updatedAt
    }
]]

local ORDER_FIELDS = [[
    fragment OrderFields on Order {
        id
        status
        total
        createdAt
    }
]]

-- Use fragments in queries
local success, response = graphql.query(
    USER_FIELDS .. ORDER_FIELDS .. [[
        query($userId: ID!) {
            getUser(id: $userId) {
                ...UserFields
                orders {
                    ...OrderFields
                }
            }
        }
    ]],
    { userId = "user-123" }
)

Error Handling

lua
function safe_graphql_query(document, variables)
    local success, response = graphql.query(document, variables)

    if not success then
        print("GraphQL request failed completely")
        return nil, "Request failed"
    end

    -- Check for GraphQL errors
    if response.errors and #response.errors > 0 then
        local error_messages = {}
        for _, error in ipairs(response.errors) do
            table.insert(error_messages, error.message)

            -- Check for specific error types
            if error.extensions then
                if error.extensions.code == "UNAUTHENTICATED" then
                    return nil, "Authentication required"
                elseif error.extensions.code == "FORBIDDEN" then
                    return nil, "Permission denied"
                elseif error.extensions.code == "BAD_USER_INPUT" then
                    return nil, "Invalid input: " .. error.message
                end
            end
        end

        return nil, table.concat(error_messages, "; ")
    end

    -- Check for empty data
    if not response.data then
        return nil, "No data returned"
    end

    return response.data, nil
end

-- Usage
local data, error = safe_graphql_query([[
    query($id: ID!) {
        getUser(id: $id) {
            name
            email
        }
    }
]], { id = user_id })

if data then
    print("User: " .. data.getUser.name)
else
    print("Query failed: " .. error)
end

Subscriptions (if supported)

lua
-- Note: Subscriptions typically require a persistent connection
-- This example shows the query structure
local subscription_doc = [[
    subscription($userId: ID!) {
        orderStatusUpdated(userId: $userId) {
            orderId
            oldStatus
            newStatus
            updatedAt
        }
    }
]]

-- Subscription handling would be implementation-specific

Best Practices

  1. Use variables for dynamic values: Never concatenate strings into queries

    lua
    -- BAD: String concatenation
    local query = 'query { getUser(id: "' .. user_id .. '") { name } }'
    
    -- GOOD: Use variables
    graphql.query([[
        query($id: ID!) {
            getUser(id: $id) { name }
        }
    ]], { id = user_id })
  2. Request only needed fields: Minimize data transfer

    lua
    -- Request only what you need
    local success, response = graphql.query([[
        query($id: ID!) {
            getUser(id: $id) {
                name  -- Only fetch name if that's all you need
            }
        }
    ]], { id = user_id })
  3. Handle nullable fields: Check for nil values

    lua
    local success, response = graphql.query([[
        query($id: ID!) {
            getUser(id: $id) {
                name
                phone  -- May be null
            }
        }
    ]], { id = user_id })
    
    if success and response.data then
        local user = response.data.getUser
        local phone = user.phone or "No phone number"
    end
  4. Batch related queries: Reduce round trips

    lua
    -- Fetch multiple related entities in one query
    local success, response = graphql.query([[
        query($userId: ID!, $orderId: ID!) {
            user: getUser(id: $userId) {
                name
                email
            }
            order: getOrder(id: $orderId) {
                status
                total
            }
        }
    ]], { userId = user_id, orderId = order_id })

Integration Examples

User Authentication Flow

lua
function authenticate_user(email, password)
    local success, response = graphql.query([[
        mutation($email: String!, $password: String!) {
            authenticate(email: $email, password: $password) {
                success
                token
                user {
                    id
                    name
                    roles
                }
            }
        }
    ]], { email = email, password = password })

    if success and response.data then
        local auth = response.data.authenticate
        if auth.success then
            -- Store token for future requests
            cache.put_ttl("auth:token:" .. auth.user.id, auth.token, 3600)
            return auth.user, auth.token
        end
    end

    return nil, nil
end

Data Synchronization

lua
function sync_customer_data(customer_id)
    -- Fetch from GraphQL
    local success, response = graphql.query([[
        query($id: ID!) {
            getCustomer(id: $id) {
                id
                name
                email
                phone
                address {
                    street
                    city
                    postalCode
                    country
                }
                orders {
                    id
                    total
                    status
                }
            }
        }
    ]], { id = customer_id })

    if not success or not response.data then
        return false, "Failed to fetch customer data"
    end

    local customer = response.data.getCustomer

    -- Sync to database
    local db_success, _ = database.query(
        "sync-db",
        [[
            INSERT INTO customers (id, name, email, phone, address_json, order_count, total_spent)
            VALUES (?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT (id) DO UPDATE SET
                name = EXCLUDED.name,
                email = EXCLUDED.email,
                phone = EXCLUDED.phone,
                address_json = EXCLUDED.address_json,
                order_count = EXCLUDED.order_count,
                total_spent = EXCLUDED.total_spent,
                synced_at = NOW()
        ]],
        {
            customer.id,
            customer.name,
            customer.email,
            customer.phone,
            encoding.json(customer.address),
            #customer.orders,
            calculate_total_spent(customer.orders)
        }
    )

    return db_success, db_success and "Sync completed" or "Database sync failed"
end

Security and Performance

Security:

  • Uses current actor's permissions for all GraphQL operations
  • Schema-level authorization rules are enforced
  • Rate limiting may apply based on schema configuration
  • Input validation is performed by the GraphQL schema

Performance:

  • Queries are parsed and validated once per execution
  • Consider query complexity and depth limits
  • Use DataLoader patterns for N+1 query prevention
  • Cache frequently accessed data when appropriate

Connect. Combine. Collaborate.
The pioneering open integration platform, dedicated to transforming connectivity in the printing industry.