API Connect

API Connect

Join this online group to communicate across IBM product users and experts by sharing advice and best practices with peers and staying up to date regarding product enhancements.


#API Connect
#Applicationintegration
#APIConnect
 View Only

Persisted Documents with API Connect For GraphQL

By Timil Titus posted 4 days ago

  

In recent years, GraphQL has revolutionized API development by enabling clients to request exactly the data they need, no more and no less. This flexibility has made GraphQL increasingly popular among developers building modern applications. However, this flexibility comes with challenges, particularly around performance optimization, bandwidth usage, and security concerns.

Enter persisted documents—a powerful approach that addresses these challenges while preserving GraphQL's flexibility. Persisted documents are pre-registered, server-stored GraphQL operations that clients can reference using a unique identifier rather than sending the entire query string with each request.

Instead of sending potentially large query strings with each request, clients send a compact document identifier. The server then retrieves the pre-validated query, executes it with the provided variables, and returns the results.

This approach offers several compelling advantages:

  1. Enhanced Security: By limiting API execution to pre-approved queries, you can prevent malicious operations and reduce attack vectors.

  2. Improved Performance: Servers can optimize execution paths for known queries in advance, eliminating the need to parse and validate queries on each request.

  3. Reduced Bandwidth: Sending a document identifier instead of complete query strings significantly reduces request payload sizes, especially important for mobile applications or low-bandwidth environments.

  4. Better Caching: Persisted documents enable the use of HTTP GET requests with predictable URLs, improving cache utilization throughout the request chain.

In this article, we'll explore how API Connect For GraphQL—a GraphQL-as-a-service platform—implements persisted documents, providing a seamless way to gain these benefits with minimal configuration. We'll walk through a practical example, discuss implementation details, and highlight best practices for incorporating persisted documents into your GraphQL architecture.

Understanding Persisted Documents in API Connect For GraphQL

API Connect For GraphQL provides built-in support for persisted documents, making them easy to implement in your GraphQL API. Let's dive into how this feature works within the API Connect For GraphQL ecosystem.

The Core Mechanism

At its core, API Connect For GraphQL's persisted documents feature allows you to:

  1. Define executable documents containing GraphQL operations (queries, mutations, subscriptions) and fragments
  2. Register these documents with your schema during deployment
  3. Reference them by identifier in client requests instead of sending the full query text

When a schema with persisted documents is deployed, API Connect For GraphQL validates each document against the schema to ensure it's valid. This validation step prevents deployment if any document contains errors, ensuring that only valid operations can be persisted.

Document Identifiers

API Connect For GraphQL uses SHA256 hashes as document identifiers. This approach has several advantages:

  • Deterministic: The same document always produces the same hash
  • Collision-resistant: Different documents produce different hashes
  • Content-based: The identifier is derived from the document content itself

The document identifier is calculated based on the exact content of the document file, including whitespace and formatting. This is why consistent formatting (using tools like Prettier) is recommended as part of your workflow.

Generating Document Identifiers

To generate a SHA256 hash for a document, you can use standard command-line tools:

shasum -a 256 operations.graphql

This command outputs the hash value, for example:

9d50d8e35b5882139e836a126f5d6d5a28cf41c5efd80a6e67f920d284b5f6d0  operations.graphql

When using this hash as a document identifier in API Connect For GraphQL, you prefix it with "sha256:" to form the complete identifier:

sha256:9d50d8e35b5882139e836a126f5d6d5a28cf41c5efd80a6e67f920d284b5f6d0

API Connect For GraphQL automatically calculates these hashes when you deploy your schema, but understanding how they're generated is useful for debugging and client-side implementation.

The @sdl Directive

In API Connect For GraphQL, persisted documents are configured using the @sdl directive in your schema definition. This directive takes an executables argument that specifies which documents should be included and whether they should be persisted.

Here's the basic syntax:

schema
  @sdl(
    files: []
    executables: [{ document: "operations.graphql", persist: true }]
  ) {
  query: Query
}

The executables argument is an array of objects, each specifying:

  • document: The path to the document file
  • persist: A boolean indicating whether the document should be persisted

When persist is set to true, API Connect For GraphQL calculates the document identifier and makes the document available for client requests using that identifier.

Client Request Format

Once documents are persisted, clients can reference them using the document identifier instead of sending the full query text. A typical request would look like:

{
  "documentId": "sha256:9d50d8e35b5882139e836a126f5d6d5a28cf41c5efd80a6e67f920d284b5f6d0",
  "operationName": "Customer",
  "variables": {
    "id": 1789
  }
}

This format works with both HTTP POST and GET requests. For GET requests, the parameters are encoded in the URL, making them cacheable by HTTP caches:

https://<environment>.us-east-a.ibm.stepzen.net/api/persisted-docs/graphql?documentId=sha256:9d50d8e35b5882139e836a126f5d6d5a28cf41c5efd80a6e67f920d284b5f6d0&operationName=Customer&variables=%7B%22id%22%3A%201789%7D

Step-by-Step Implementation Example

Let's walk through a complete example of implementing persisted documents in API Connect For GraphQL. We'll create a simple GraphQL API for customer data and demonstrate how to set up persisted documents.

1. Setting Up the Schema

First, let's define our schema in index.graphql. This file establishes the structure of our GraphQL API and configures persisted documents:

schema
  @sdl(
    files: []
    # executables defines a list of GraphQL executable documents that
    # are validated against the schema when deployed.
    # If an executable document is marked as persist: true
    # then it becomes a persisted document with the document identifier
    # being tha SHA 256 hash of the document file.
    executables: [{ document: "operations.graphql", persist: true }]
  ) {
  query: Query
}

type Query {
  customer(id: ID!): Customer
}
type Customer @mock {
  id: ID!
  name: String! @mockfn(name: "LastName")
  email: String @mockfn(name: "Email")
  phone: String @mockfn(name: "Phone")
  address: Address
}
type Address {
  city: String @mockfn(name: "City")
  zip: String @mockfn(name: "Zip")
}

Key points in this schema:

  • The @sdl directive specifies that operations.graphql should be included as an executable document and persisted
  • We define a simple data model with Customer and Address types
  • For demonstration purposes, we're using mock data with the @mock and @mockfn directives

2. Defining Operations

Next, let's create our operations in operations.graphql. This file contains the GraphQL operations that will be persisted:

query Customer($id: ID!) {
  customer(id: $id) {
    id
    name
    email
    phone
    address {
      city
      zip
    }
  }
}

query CustomerName($id: ID!) {
  customer(id: $id) {
    name
  }
}

query CustomerEmail($id: ID!) {
  customer(id: $id) {
    email
  }
}

This file defines three query operations:

  • Customer: Retrieves all customer data including address
  • CustomerName: Retrieves only the customer's name
  • CustomerEmail: Retrieves only the customer's email

Each operation takes an id parameter to specify which customer to retrieve.

3. Configuration

We also need a stepzen.config.json file to configure our API Connect For GraphQL endpoint:

{
  "endpoint": "api/persisted-docs"
}

This sets the endpoint path for our GraphQL API.

4. Deployment and Usage

  1. Login to stepzen CLI.
stepzen login <domain> -i <instance_id>
  1. Deploy the schema to API Connect For GraphQL.
stepzen deploy

Once the schema is deployed, the operations in operations.graphql will be persisted with a document identifier. In this case, the document identifier is:

sha256:9d50d8e35b5882139e836a126f5d6d5a28cf41c5efd80a6e67f920d284b5f6d0

This identifier is the SHA256 hash of the operations.graphql file (with the "sha256:" prefix added). You can verify this by running shasum -a 256 operations.graphql on your local machine.

Clients can now use this identifier to execute the operations. Here are examples of how to use each operation:

Testing with API Client Tools

You can easily test persisted documents using Postman. Here's how to set it up:

For POST Requests:

  1. Create a new POST request in Postman
  2. Set the URL to your API Connect For GraphQL endpoint (e.g., https://<environment>.us-east-a.ibm.stepzen.net/api/persisted-docs/graphql)
  3. Add the following headers:
    • Content-Type: application/json
    • Authorization: Bearer $(stepzen whoami --accesstoken)
  4. In the request body, select "raw" and "JSON", then enter:
     {
         "documentId": "sha256:9d50d8e35b5882139e836a126f5d6d5a28cf41c5efd80a6e67f920d284b5f6d0",
         "operationName": "Customer",
         "variables": {
             "id": 1789
         }
     }
    
  5. Send the request

The JSON body contains three key elements:

  • documentId: The SHA256 hash of your operations file with the "sha256:" prefix
  • operationName: The specific operation to execute from the persisted document
  • variables: Any variables needed by the operation

For GET Requests:

  1. Create a new GET request in Postman
  2. Set the URL to your API Connect For GraphQL endpoint with query parameters:
  https://<environment>.us-east-a.ibm.stepzen.net/api/persisted-docs/graphql?documentId=sha256:9d50d8e35b5882139e836a126f5d6d5a28cf41c5efd80a6e67f920d284b5f6d0&operationName=Customer&variables=%7B%22id%22%3A%201789%7D
  1. Add the header:
    • Authorization: Bearer $(stepzen whoami --accesstoken)
  2. Send the request

Note that for GET requests, the variables are URL-encoded. The %7B%22id%22%3A%201789%7D part is the URL-encoded version of {"id":"1789"}.

Troubleshooting

If you encounter errors:

  • Verify your API key is correct
  • Ensure the document identifier matches the SHA256 hash of your operations file
  • Check that the operation name exists in your persisted document
  • Confirm that the variables match what the operation expects

Remember that the document identifier is case-sensitive and must include the "sha256:" prefix.

This would return:

{
  "data": {
    "customer": {
      "id": "1789",
      "name": "Considine",
      "email": "sincerehermiston@bailey.biz",
      "phone": "3880338821",
      "address": {
        "city": "Kuhnport",
        "zip": "50935"
      }
    }
  }
}

5. Using HTTP GET

One of the advantages of persisted documents is the ability to use HTTP GET requests. Here's an example using curl:

curl \
   --header "Authorization: Bearer $(stepzen whoami --accesstoken)" \
   --header "Content-Type: application/json" \
 'https://<environment>.us-east-a.ibm.stepzen.net/api/persisted-docs/graphql?documentId=sha256:9d50d8e35b5882139e836a126f5d6d5a28cf41c5efd80a6e67f920d284b5f6d0&operationName=Customer&variables=%7B%22id%22%3A%201789%7D'

This GET request includes:

  • The document identifier
  • The operation name
  • URL-encoded variables

The ability to use GET requests enables better caching throughout your infrastructure, from CDNs to browsers.

Key Takeaways

  1. Balance of Flexibility and Control: Persisted documents offer a thoughtful compromise between the unlimited flexibility of traditional GraphQL and the rigid structure of REST APIs. They preserve GraphQL's powerful querying capabilities while adding layers of security, performance, and control.

  2. Significant Performance Benefits: By eliminating query parsing and validation overhead, enabling HTTP caching, and reducing bandwidth requirements, persisted documents can dramatically improve API performance, especially for high-traffic applications.

  3. Enhanced Security Posture: Restricting API execution to pre-approved operations significantly reduces the attack surface and protects against common GraphQL-specific vulnerabilities.

  4. Seamless Implementation with API Connect For GraphQL: API Connect For GraphQL's built-in support for persisted documents makes implementation straightforward, requiring minimal configuration changes to your existing GraphQL schema.

  5. Ideal for Mobile and Public APIs: The benefits of persisted documents are particularly valuable for mobile applications and public-facing APIs, where bandwidth, performance, and security are critical concerns.

API Connect For GraphQL's implementation of persisted documents provides a glimpse of this future—a GraphQL ecosystem that combines the developer experience benefits of GraphQL with the performance and security characteristics required for mission-critical applications.

Conclusion

If you're interested in implementing persisted documents in your GraphQL API, API Connect For GraphQL makes it easy to get started:

  1. Identify the operations your clients need
  2. Add them to your schema using the @sdl directive
  3. Deploy your schema
  4. Update your clients to use document identifiers

The initial investment is minimal, but the benefits in terms of performance, security, and operational efficiency can be substantial.

By embracing persisted documents, you're not just optimizing your current API—you're preparing for a future where GraphQL powers an increasingly large portion of the world's APIs, from mobile applications to IoT devices, enterprise systems, and beyond. Persisted documents represent an important evolution in GraphQL API development, bridging the gap between the flexibility of GraphQL and the performance and security requirements of production applications.

0 comments
0 views

Permalink