Four advantages of jq for declarative GraphQL configuration

GraphQL is a query language for APIs that was developed by Facebook in 2012 and later released as an open-source project in 2015. It provides a powerful and intuitive way to describe the shape of data that your client needs from an API, allowing clients to ask for exactly what they need and nothing more. Unlike traditional REST APIs, which often require multiple round-trip requests to fetch related data, GraphQL allows you to fetch all the necessary data in a single request. This makes it a popular choice for building modern web and mobile applications that require efficient data fetching and a flexible data model. In a GraphQL API, resolvers are responsible for fetching the data that the client requested, and they can be written in a variety of programming languages.

Declarative resolvers are an increasingly popular way of defining GraphQL APIs, providing a simpler, more flexible, and more efficient approach to data transformation and retrieval. At the same time, there are a number of different tools and approaches available for implementing declarative resolvers, with some developers opting for programmatic solutions and others choosing to use tools like jq. In this article, we’ll explore why to chose to use jq for declarative resolvers, and the benefits that this approach offers.

How to automate and scale with declarative APIs

Kubernetes has seen widespread adoption in recent years, and with it, the rise of declarative APIs and configuration models in the infrastructure industry. This approach has quickly become a popular pattern for managing infrastructure, as it allows operators to manage infrastructure as code, providing the ability to version, audit, and roll back changes with ease. One of the key benefits of declarative configuration, when combined with GitOps processes, is the ability to manage your infrastructure in a highly automated and scalable manner. As a result, there is a growing trend of platform operators and infrastructure administrators adopting the declarative paradigm, which has led to an increased appetite for configuring GraphQL components using this mechanism.

Declarative configuration provides a clear and concise way to express the desired state of a system, without requiring a detailed understanding of the underlying implementation details. By describing what you want, rather than how to get there, declarative configuration allows for a more declarative and less procedural approach to infrastructure management. This approach has proven to be highly effective in Kubernetes, where declarative configuration is the preferred way of managing resources such as deployments, services, and ingress rules.

Managing GraphQL with declarative configuration, resolvers, & jq

With the growing popularity of GraphQL, there is a corresponding need for a declarative way to manage GraphQL components. By leveraging declarative configuration, developers and operators can manage GraphQL schemas, resolvers, and other components as code, making it easier to version, test, and deploy changes in a reliable and repeatable way. Additionally, declarative configuration allows for a more modular approach to building GraphQL APIs, which can help to improve scalability, reliability, and maintainability.

Declarative resolvers

First, let’s take a look at what we mean by declarative resolvers. In GraphQL, resolvers are responsible for fetching the data that’s requested in a query, mutation, or subscription. Declarative resolvers are a way of defining this data retrieval process in a declarative manner, specifying the expected output based on the input, without having to write complex logic or manage side-effects.

Declarative GraphQL Configuration

Programmatic resolvers

Programmatic (aka imperative) resolvers, on the other hand, rely on custom code to fetch and manipulate the data, often using imperative or functional programming techniques. While programmatic resolvers offer a high degree of flexibility and control over the data retrieval process, they can also be more complex to understand and maintain, and may require more development time and resources.

What declarative configuration looks like:

Declarative Configuration

vs. imperative:

Imperative Configuration

While imperative might seem familiar and simpler at first, it can often get complicated fast for transforming data, which is better served by a native data transformation language like jq.

The magic of jq

jq is a powerful and flexible command-line tool for parsing and manipulating JSON data. It is open source, with the source code available here, and an interactive playground available here.

So, why did we choose to use jq for declarative resolvers? We found four impactful ways that jq made life easier.

Four key reasons to choose jq

1. Simplicity

jq is a powerful library that allows you to manipulate and transform JSON data easily. When working with GraphQL APIs, we may receive data in a JSON format that needs to be transformed before returning it to the client. jq can help simplify this process by allowing us to select, filter, and manipulate the data using a concise syntax. This makes it easier to understand and maintain our resolvers, reducing the likelihood of introducing bugs.

2. Reusability

Declarative resolvers can be reused across multiple queries, mutations, or subscriptions. Since the resolver is based on the schema definition, it can be used wherever the same type or field is requested. jq can help us define declarative resolvers that are reusable and can be easily shared across different parts of our API, reducing development time and improving consistency.

3. Flexibility

jq is more flexible than programmatic resolvers. With jq, we can modify the data in a variety of ways, such as filtering, sorting, or transforming, without having to write custom code for each operation. This makes it easier to handle complex data transformations and adapt to changing requirements.

4. Performance

jq is designed to be fast and efficient, making it a good choice for data processing in high-performance environments. Since declarative resolvers specify the expected output based on the input, the server can optimize the data fetching and caching process to improve performance, and jq can help us efficiently process large data sets, reducing network latency and improving user experience.

For a real example of how we’ve implemented this in our Gloo GraphQL product, head over to the GraphQL jq docs, and work through the example there.

Overall, using jq for declarative resolvers offers a number of advantages over programmatic solutions, including simplicity, reusability, flexibility, and performance. By leveraging this powerful tool, we can build more efficient and maintainable GraphQL APIs, with reduced development time and improved reusability.