February 6, 2019 | Written by: Erik Wittern
Categorized: Thomas J Watson Research Center
Share this post:
GraphQL is an increasingly popular technology for building developer-friendly, flexible to use, and easily extensible APIs. The number of publicly accessible GraphQL APIs is also growing, as exemplified by APIs provided by GitHub, Yelp, or Shopify.
A challenge that GraphQL API providers face, though, is managing their APIs. How can they ensure that malicious requests, that would result in extensive computational load and huge response sizes, do not burden their servers? How do they rate limit requests to their API? How do they price them?
API Management: REST(like) vs. GraphQL
For REST(-like) APIs, gateways like the Microgateway or Kong and API management solutions like IBM API Connect, Apigee, or 3scale fulfill these needs: they allow users to define rate-limits and prices for request, enforce authentication and authorization, and block clients that attempt to misuse an API. Client allowances are typically defined in policies, that either address requests in general or requests to specific API operations (identified by combinations of a URL path and HTTP method). For GraphQL, such solutions are currently not available. One reason is the challenge of understanding what a GraphQL request intends to do. Consider the following pairs of equivalent requests:
It is easy to define policies for the exemplary requests to the REST(-like) API based on the operation they target. The first request, which retrieves (
GET) profile data of the logged-in user (
../profiles/me), may be priced less than the second request, which leads to the creation (
POST) of a Kubernetes cluster (
../resources/k8cluster). The third example encompasses multiple requests, one to retrieve a list of 5 users, and five further requests to retrieve the employer company for each of those users (identified by their
ids). Rate limits and prices would naturally reflect these numerous requests.
The exemplary GraphQL requests, which do the same thing, cannot be differentiated based on the operation they invoke: they all perform a single
POST request to
../graphql (in the third example query, that single request is equivalent to 6 REST(-like) API requests). Rather, their intentions are encoded in the query sent in the request payload. Thus, for GraphQL API management, we need to be able to assess queries before they are executed.
Our approach to enable GraphQL API management is to perform static analysis on queries: we parse incoming queries, analyze their abstract syntax trees, and extract characteristics that can be matched against policies defined by the API provider. If a query adheres to the policies, it is allowed and forwarded to the GraphQL backend, and remaining rate limits and costs are eventually updated. If not, the request is blocked and the client receives an error message. In addition, the GraphQL API management facilities can inspect a query, i.e., to analyze but never execute it. This allows to build tools that help client developers design valid queries.
Our static query analysis extracts the following characteristics:
- Type complexity: A weighted sum of the amount of resources returned by a query. In the third example query above, it is 10, as the query retrieves 5 users and 5 employer companies (one for each user).
- Resolve complexity: A weighted sum of the amount of resolver functions invoked in the GraphQL server to execute the query. In the third example query above, it is 6, as one resolver function is invoked to retrieve 5 users, and five additional resolver functions are invoked to retrieve the employer company of each of these users.
- Nesting: The maximum nesting level of a query. In the third example query above, it is 2.
- Type counts: Counts of the GraphQL Object Types that the query returns.
- Resolver counts: Counts of the specific resolver functions invoked when executing the query.
Using these characteristics, numerous policies can be defined. Rate limits and request costs can be expressed based on type and/or resolve complexities. Malicious requests can be blocked based on complexities and nesting. Eventually, access control can be based on type and resolver counts.
Beyond a query, our analysis facilities rely on two additional inputs: First, the schema of the GraphQL backend. Fortunately, an (up-to-date) schema can easily be obtained from any GraphQL specification-compliant backend via introspection. Second, the GraphQL API provider can provide a configuration. It contains, on the one hand, information required to correctly extract characteristics. For example, the analysis needs to be configured to consider the value (here:
5) of the
limit argument in the third example query above to correctly multiply child selections (we call these multiplier arguments). Additional configuration is required to deal with connections for pagination, where multiplication does not affect direct children of fields with multiplier arguments. On the other hand, providers can fine-tune the analysis to their needs. For example, they can assign custom weights to types and resolver functions to impact how complexities are calculated.
Estimated vs. actual characteristics
One additional challenge is that query characteristics express upper bounds for complexities, nesting, and counts. A query may turn out, however, to be less complex than expected. Consider the third example query above once more. If the GraphQL backend contains only 2 users, the actual complexities and counts from executing it are lower than expected (the type complexity would be 4 instead of 10, and the resolve complexity would be 3 instead of 6).
Upper bounds are useful to block malicious requests, but are problematic for pricing and rate limiting. If an allowed query turns out to be less complex to execute than expected, rate limits and costs should be calculated accordingly to avoid overcharging clients. To achieve this, we analyze the responses of queries and extract the same set of characteristics from them – only this time, they are based on the data actually produced by the GraphQL server. Doing so allows us to accurately update remaining rate limits and price requests.
Learn more at THINK
We are in the process of implementing the here described GraphQL API Management capabilities as part of IBM API Connect and IBM DataPower. We will show a running demo at THINK 2019 in San Francisco! If you want to learn more about this work, visit THINK session 7929A on Thursday, Feb. 14th, 3:30 pm – 4:10 pm (Moscone South, Exhibit Level, Hall C) or reach out to us on Twitter @erikwittern or @laredoj.
Also, check out OASGraph, IBM’s open source library to wrap existing REST(-like) APIs with GraphQL.