Introduction

Recently, at the ASC 2021 API Specifications Conference, Anant Jhingran gave a talk about bridging the gap between the OpenAPI spec and GraphQL. Here we'll deep dive into the code behind the talk to explore some practical ways of briding the gap.

A Critical Problem that Arises in API Design

The conventions of backend and frontend developers create a central problem in their communication process. Backend engineers, with a "producer's" perspective, are often concerned with completeness - are they surfacing all the data possible? Meanwhile, frontend engineers look for simplicity. Is it clear which methods are available to surface the data they need to power the experiences they build? They have the "consumer's" perspective.

Neither the frontend nor the backend view is perfect. The former can eliminate a lot of useful information and require manual mapping. The latter can flood the frontend developer with a lot of interfaces, oftentimes with complicated, auto-generated names. API design requires an approach that bridges teh gap.

An Example of this Conflict of Perspective

The Frontend

Say the consumer is looking for the weather temperature and description from a Weather API to surface it to a widget on their UI - the only thing they'll be concerned with is a single graphql query:

type Query {
   weather(city:String!):WeatherReport
}

type WeatherReport {
   temp: Float
   description: String
}

The Backend

But starting from the backend, from the producer's perspective, the developer is often using the OpenAPI specification, which defines a common interface for REST APIs that makes it easy to introspect them. Thinking in terms of creating a complete schema, the backend engineer can autogenerate the schema for a REST API with an OpenAPI spec with dozens and dozens of queries.

Our Example

Let's take a case where a fullstack team is making a connection to two different APIs for a project.

The goal is to use a Cloudmersive API to detect the language of a given DEV.to comment.

Here's a snippet of code from the schema that Anant built for this example. An extension technically accomplishes the goal:

extend type Devto_getArticlesResponse {
    Language(
        cloudmersive_apiKey: Secret!
    ): Cloudmersive_LanguageDetectionResponse
        @materializer(
            query: "cloudmersive_LanguageDetection_getLanguage"
            arguments: [
                { name: "cloudmersive_apiKey", argument: "cloudmersive_apiKey" }
                { name: "textToDetect", field: "description" }
            ]
        )
}

Note: here the StepZen custom directive @materializer surfaces the result of the cloudmersive language detection query to the Devto_getArticlesResponse type, no resolvers necessary.

The backend developers have a plan: because both of these APIs participate in the OpenAPI spec, they can easily introspect both of them for complete coverage.

The Open API spec represents years of investment. The designers of these APIs were thinking about the data that the union of all users wanted, not about each individual user separately from the whole. They also carefully considered other needs, including access control, performance, and error handling.

This means that our backend engineers' introspection of both APIs results in a multitude of queries! This is dizzying for the frontend developer who just wants one result from these connections. Ironically, this gap in API design is created by too much coverage.

screenshot of dozens of queries in the graphiql editor

It also means that our team needs to adopt a curation process to make the GraphQL API workable.

With StepZen, curating the API implemention to a manageable number of queries is straightforward.

'Curating' here means pruning the API, transforming it from completeness for the backend to accuracy for the frontend. What queries are needed, and what aren't?

Closing the Gap in API Design

Once the developer knows which queries they want to emphasize, it's a quick process to comment out unneeded queries in GraphQL files, and create new 'curated' schemas. And with StepZen, preventing them from surfacing to the endpoint is the same process.

To make schemas accessible to an endpoint deployed on StepZen, a developer specifies them in a file called index.graphql.

schema
  @sdl(
    files: [
      "curateddevto.graphql"
      "curatedcloudmersive.graphql"
      # "devto.graphql"
      # "cloudmersive.graphql"
      # "extend.graphql"
    ]
  ) {
  query: Query
}

As you can see, you can comment out the non-curated versions of the APIs, leaving us with a more manageable query set.

screenshot of two queries in the graphiql

With StepZen's index.graphql file, backend engineers can generate a complete schema based on the OpenAPI spec, and enable the frontend engineers with "views" of the data they need to accomplish their goals.

Conclusion

Producers (typically, backend engineers) and consumers (typically, frontend engineers) have different "views" of the data they're exposing or consuming - the language, perspective, and implementations of the sides are different. Therefore they can run into a "communication gap" when it comes to implementing GraphQL schemas with the OpenAPI spec. But bridging the gap is possible. Furthermore, bridging the gap enables developers to leverage investment in REST and OpenAPI and take advantage of the flexibility and developer experience of GraphQL.

For more on this topic see: