Skip to main content

JQ Query Endpoint

The /jq-query REST endpoint provides a powerful way to execute GraphQL queries with server-side JQ transformations. Unlike the built-in jq() GraphQL query, this endpoint returns the transformation result directly as the HTTP response and provides access to the complete GraphQL response (including errors) within JQ expressions.

Overview

The /jq-query endpoint is designed for scenarios where you need:

  • Direct transformation results without GraphQL envelope
  • Access to GraphQL errors in JQ expressions
  • HTTP-based caching control via headers
  • Standalone data processing pipelines
  • Integration with non-GraphQL clients

Endpoint Details

Method: POST

URL: /jq-query

Content-Type: application/json

Request Format

Headers

Required Headers

Content-Type: application/json

Optional Authentication

Authorization: Bearer <token>

Use when your hugr instance requires authentication.

Optional Caching Headers

When hugr has caching configured (see Caching Configuration), you can control caching behavior:

X-Hugr-Cache: <ttl_seconds or duration>
X-Hugr-Cache-Key: <custom_cache_key>
X-Hugr-Cache-Tags: <tag1,tag2,tag3>
X-Hugr-Cache-Invalidate: <true|false>

Caching Headers Reference:

HeaderTypeDescription
X-Hugr-CacheStringCache TTL (time-to-live). Accepts seconds (e.g., 300) or duration format (e.g., 5m, 1h, 30s).
X-Hugr-Cache-KeyStringCustom cache key. If omitted, computed as hash of request body. User's role is automatically appended.
X-Hugr-Cache-TagsStringComma-separated list of tags for grouped cache invalidation (e.g., users,analytics,reports).
X-Hugr-Cache-InvalidateBooleanWhen true, invalidates existing cache entry and re-executes query.

Cache Key Format: <custom_key or hash(request_body)>:<user_role>

Request Body

{
"jq": "<jq_expression>",
"query": {
"query": "<graphql_query>",
"variables": {
"var1": "value1",
"var2": "value2"
},
"operationName": "<operation_name>"
}
}

Fields:

  • jq (string, required): JQ expression to apply to the GraphQL response
  • query (object, required): GraphQL query details
    • query (string, required): GraphQL query text
    • variables (object, optional): Query variables
    • operationName (string, optional): Operation name for named queries

JQ Input

The JQ expression receives the complete GraphQL response, including:

  • data - Query results
  • extensions - Extensions object
  • errors - GraphQL errors (if any)

This is different from the built-in jq() query, which only receives the data field.

Response Format

Success Response

HTTP Status: 200 OK

Body: The result of the JQ transformation (direct output, no GraphQL envelope)

<jq_transformation_result>

The response format depends on your JQ expression:

  • Array if JQ returns an array
  • Object if JQ returns an object
  • Primitive value if JQ returns a string/number/boolean

Error Response

HTTP Status: 400 Bad Request, 401 Unauthorized, or 500 Internal Server Error

Body:

{
"error": "Error message description"
}

HTTP Status Codes

CodeDescription
200 OKSuccessful transformation
400 Bad RequestInvalid JQ expression or GraphQL query syntax
401 UnauthorizedMissing or invalid authentication
500 Internal Server ErrorServer-side error during processing

Examples

Basic Query

Execute a simple GraphQL query and transform the result:

curl -X POST http://localhost:8080/jq-query \
-H "Content-Type: application/json" \
-d '{
"jq": ".data.users | map({id, name})",
"query": {
"query": "{ users { id name email created_at } }"
}
}'

Response:

[
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"},
{"id": 3, "name": "Charlie"}
]

Query with Variables

Use GraphQL variables and access them in JQ:

curl -X POST http://localhost:8080/jq-query \
-H "Content-Type: application/json" \
-d '{
"jq": ".data.users | map(select(.country == $country))",
"query": {
"query": "query($country: String!) { users { id name country } }",
"variables": {
"country": "USA"
}
}
}'

Response:

[
{"id": 1, "name": "Alice", "country": "USA"},
{"id": 5, "name": "David", "country": "USA"}
]

Error Handling

Handle GraphQL errors gracefully in JQ:

curl -X POST http://localhost:8080/jq-query \
-H "Content-Type: application/json" \
-d '{
"jq": "if .errors then {error: \"Query failed\", details: .errors} else .data.users end",
"query": {
"query": "{ users { id name invalid_field } }"
}
}'

Response (when query has errors):

{
"error": "Query failed",
"details": [
{
"message": "Cannot query field 'invalid_field' on type 'User'",
"locations": [{"line": 1, "column": 20}]
}
]
}

With Authentication

Include Bearer token for authenticated requests:

curl -X POST http://localhost:8080/jq-query \
-H "Content-Type: application/json" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-d '{
"jq": ".data.orders | map({id, total, customer: .customer.name})",
"query": {
"query": "{ orders { id total customer { name } } }"
}
}'

Caching with TTL

Cache results for 5 minutes:

curl -X POST http://localhost:8080/jq-query \
-H "Content-Type: application/json" \
-H "X-Hugr-Cache: 5m" \
-H "X-Hugr-Cache-Tags: users,analytics" \
-d '{
"jq": ".data.users_aggregation",
"query": {
"query": "{ users_aggregation { _rows_count total_revenue { sum } } }"
}
}'

First request: Executes query, caches result for 5 minutes Subsequent requests (within 5 min): Returns cached result instantly

Custom Cache Key

Use meaningful cache keys for dashboard queries:

curl -X POST http://localhost:8080/jq-query \
-H "Content-Type: application/json" \
-H "X-Hugr-Cache: 300" \
-H "X-Hugr-Cache-Key: dashboard:daily-stats:2024-01-15" \
-H "X-Hugr-Cache-Tags: dashboard,analytics,daily" \
-d '{
"jq": ".data | {users: .users_aggregation._rows_count, orders: .orders_aggregation._rows_count, revenue: .orders_aggregation.total.sum}",
"query": {
"query": "{ users_aggregation { _rows_count } orders_aggregation { _rows_count total { sum } } }"
}
}'

Cache Key: dashboard:daily-stats:2024-01-15:<user_role> TTL: 300 seconds (5 minutes)

Cache Invalidation

Force cache refresh when underlying data changes:

curl -X POST http://localhost:8080/jq-query \
-H "Content-Type: application/json" \
-H "X-Hugr-Cache: 1h" \
-H "X-Hugr-Cache-Key: product-catalog" \
-H "X-Hugr-Cache-Invalidate: true" \
-d '{
"jq": ".data.products | group_by(.category)",
"query": {
"query": "{ products { id name category price } }"
}
}'

This request:

  1. Invalidates cache for key product-catalog:<role>
  2. Executes the query
  3. Caches new result with 1-hour TTL

Complex Transformation

Transform nested data into flat structure:

curl -X POST http://localhost:8080/jq-query \
-H "Content-Type: application/json" \
-d '{
"jq": ".data.customers | map(.orders[] | {customer_id: .customer_id, customer_name: .customer_name, order_id: .id, order_total: .total}) | flatten",
"query": {
"query": "{ customers { id name as customer_name orders { id customer_id: id total } } }"
}
}'

Aggregation and Grouping

Group and aggregate data:

curl -X POST http://localhost:8080/jq-query \
-H "Content-Type: application/json" \
-H "X-Hugr-Cache: 10m" \
-d '{
"jq": ".data.orders | group_by(.status) | map({status: .[0].status, count: length, total: map(.amount) | add, avg: (map(.amount) | add / length)})",
"query": {
"query": "{ orders { id status amount } }"
}
}'

Response:

[
{
"status": "completed",
"count": 150,
"total": 45000,
"avg": 300
},
{
"status": "pending",
"count": 25,
"total": 7500,
"avg": 300
},
{
"status": "cancelled",
"count": 10,
"total": 2000,
"avg": 200
}
]

Use Cases

Data Export Pipelines

Transform GraphQL data into formats required by external systems:

curl -X POST http://localhost:8080/jq-query \
-H "Content-Type: application/json" \
-d '{
"jq": ".data.orders | map({order_id: .id, customer: .customer.email, items: (.items | map(.product.name) | join(\", \")), total: .total})",
"query": {
"query": "{ orders { id total customer { email } items { product { name } } } }"
}
}' > orders_export.json

Dashboard Data Feed

Prepare data for BI dashboards with caching:

curl -X POST http://localhost:8080/jq-query \
-H "Content-Type: application/json" \
-H "X-Hugr-Cache: 15m" \
-H "X-Hugr-Cache-Key: dashboard:sales-overview" \
-d '{
"jq": "{summary: {total_orders: .data.orders_aggregation._rows_count, total_revenue: .data.orders_aggregation.total.sum, avg_order_value: (.data.orders_aggregation.total.sum / .data.orders_aggregation._rows_count)}, by_status: (.data.status_breakdown.key | map({status: .status, count: .aggregations._rows_count}))}",
"query": {
"query": "{ orders_aggregation { _rows_count total { sum } } status_breakdown: orders_bucket_aggregation { key { status } aggregations { _rows_count } } }"
}
}'

Webhook Data Transformation

Transform data before sending to webhooks:

# Get transformed data
DATA=$(curl -s -X POST http://localhost:8080/jq-query \
-H "Content-Type: application/json" \
-d '{
"jq": ".data.new_orders | map({id, customer_email: .customer.email, total})",
"query": {
"query": "{ new_orders: orders(filter: {status: {eq: \"new\"}}) { id total customer { email } } }"
}
}')

# Send to webhook
curl -X POST https://example.com/webhook \
-H "Content-Type: application/json" \
-d "$DATA"

Report Generation

Generate structured reports:

curl -X POST http://localhost:8080/jq-query \
-H "Content-Type: application/json" \
-H "X-Hugr-Cache: 1h" \
-d '{
"jq": "{report_date: now | strftime(\"%Y-%m-%d\"), metrics: {total_users: .data.users_aggregation._rows_count, active_users: .data.active_users._rows_count, conversion_rate: ((.data.orders_aggregation._rows_count / .data.users_aggregation._rows_count) * 100 | floor)}}",
"query": {
"query": "{ users_aggregation { _rows_count } active_users: users_aggregation(filter: {last_login: {gte: \"2024-01-01\"}}) { _rows_count } orders_aggregation { _rows_count } }"
}
}'

Comparison with Built-in jq() Query

FeatureBuilt-in jq()/jq-query Endpoint
JQ InputOnly data fieldComplete GraphQL response (data, extensions, errors)
Response FormatGraphQL response with extensions.jqDirect JQ result as HTTP response
Original DataIn data fieldNot preserved (unless included in JQ)
Error AccessNot available to JQAvailable in .errors field
CachingVia @cache directiveVia HTTP headers (X-Hugr-Cache-*)
Use CaseInline transformations in GraphQLStandalone transformations, data pipelines
Client IntegrationGraphQL clientsAny HTTP client (curl, fetch, axios)
ChainingEasy (nested jq queries)Manual (multiple requests)

Best Practices

1. Use Appropriate Cache TTL

Choose cache duration based on data volatility:

# Real-time data: short TTL
X-Hugr-Cache: 30s

# Semi-static data: medium TTL
X-Hugr-Cache: 5m

# Static/reference data: long TTL
X-Hugr-Cache: 1h

Use tags for easier cache management:

# Tag by feature area
X-Hugr-Cache-Tags: dashboard,sales,analytics

# Invalidate all dashboard queries later
# (implementation depends on cache backend)

3. Meaningful Cache Keys

Use descriptive keys for better observability:

# Good: Descriptive
X-Hugr-Cache-Key: report:monthly-sales:2024-01

# Less optimal: Auto-generated
# (omit header to use request body hash)

4. Handle Errors in JQ

Always check for GraphQL errors:

"jq": "if .errors then {error: .errors[0].message} else .data.users end"

5. Filter in GraphQL, Not JQ

Apply filters in GraphQL for better performance:

# Good: Filter in GraphQL
{
"jq": ".data.users | map({id, name})",
"query": {
"query": "{ users(filter: {active: {eq: true}}) { id name } }"
}
}

# Less efficient: Filter in JQ
{
"jq": ".data.users | map(select(.active == true)) | map({id, name})",
"query": {
"query": "{ users { id name active } }"
}
}

6. Limit Data Volume

Use GraphQL limit to reduce processing:

{
"query": {
"query": "{ users(limit: 100) { id name } }"
}
}

7. Invalidate Cache on Data Changes

Force refresh when source data changes:

# After updating products
curl -X POST http://localhost:8080/jq-query \
-H "X-Hugr-Cache: 1h" \
-H "X-Hugr-Cache-Key: product-catalog" \
-H "X-Hugr-Cache-Invalidate: true" \
-d '{...}'

Security Considerations

Authentication

Always use authentication for production:

curl -X POST http://localhost:8080/jq-query \
-H "Authorization: Bearer $TOKEN" \
-d '{...}'

Access Control

JQ transformations respect the same access control as GraphQL queries. Users can only access data they're authorized to see.

Sensitive Data

Remove sensitive fields in JQ expressions:

"jq": ".data.users | map({id, name} | del(.ssn, .password))"

Rate Limiting

Implement rate limiting on the /jq-query endpoint to prevent abuse.

Troubleshooting

Invalid JQ Expression

Error: 400 Bad Request - "Invalid JQ expression"

Solution: Test your JQ expression using the jq command-line tool or jqplay.org

Cache Not Working

Issue: Cache headers ignored

Checklist:

  1. Verify caching is configured: Caching Configuration
  2. Check CACHE_L1_ENABLED or CACHE_L2_ENABLED is true
  3. Ensure valid TTL format (e.g., 5m, 300)

Empty Response

Issue: Endpoint returns empty result

Debug:

# Check the full GraphQL response
"jq": "."

# Then narrow down
"jq": ".data"

Timeout Errors

Issue: Request times out

Solutions:

  • Add limit to GraphQL query
  • Filter data in GraphQL, not JQ
  • Reduce query complexity
  • Consider pagination

See Also

Documentation

Examples

Tools