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 documentvariables(table): Variables to pass to the GraphQL query (optional, defaults to empty table)
Returns:
[success, result]where:success(boolean):trueif query executed,falseon errorresult(table): GraphQL response containing:data(table): Query results if successfulerrors(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
endMutations
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
endPagination
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)
endSubscriptions (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-specificBest Practices
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 })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 })Handle nullable fields: Check for nil values
lualocal 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" endBatch 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
endData 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"
endSecurity 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