When you read about GraphQL today, you’ll quickly discover the main strong points it offers that the average GraphQL API user can take advantage of:
Implementing a GraphQL API is a different story though. For implementers, you’ll most likely come across the following advice:
"Map your database schema 1-to-1 to your types."
On our side, however, it’s not as straighforward since our content infrastructure lets the users define the structure of their content freely. This means we could be serving a particular user a very flat data entry structure while delivering complete content trees reaching several levels deep to another user. This flexibility means we deal with data structures of all kinds, making support for GraphQL trickier – We now have to create GraphQL schemas on the fly and deal with domain objects based on abstract syntax trees instead of simply mapping a database schema to GraphQL. If this sounds complicated, don't worry. This article will cover everything in detail.
Author’s Note: This article is based on a meetup talk I gave; a recording of the talk is linked at the end of this article.
The foundation of any GraphQL API is an abstract syntax tree, which is heavily used server-side to deal with schema definitions and parsing the actual GraphQL query.
For me, the word abstract syntax tree (AST) is just a fancy way of describing deeply nested objects that hold all the information about some source code—or in our case, GraphQL queries.
When we look at the following GraphQL query…
...the resulting abstract syntax tree (don’t worry too much about it) looks like this:
The AST includes a lot of metadata, such as location in the source, or identifiers, such as argument names. And thanks to this deeply-nested JSON object, we now have all the power we need to work with GraphQL schemas and queries. All that meta information comes in handy when developing your own GraphQL server. For instance, from that, we can easily tell you the line of your query that’s causing problems.
For the schema, these POJOs (Plain Old JSON Objects) are usually translated into so-called domain objects. They encapsulate the information contained in the AST, but are enriched with methods and are proper instances of the GraphQL base types. For example, every type that has fields to select from will be created as a GraphQLObjectType instance. Now you can define a function on it how data should be fetched.
Lets say your API gives you location data in cartesian and geographic values as "location". For your GraphQL
Location type you always want to serve back geographic coordinates, so you define a
makeLocationFieldResolver like the following:
If our type definitions are available in the System Definition Language (SDL) format, we can construct the AST from it and assign resolvers to fields by using a nested object that has functions as its leaf-most values:
Of course it has to be a little different at Contentful, given that we don’t have a System Definition Language (SDL) at hand that we can parse. So what we do is simply creating those domain objects "by hand", based on the content model we obtain from the database.
"What about the line numbers for my errors? 😱" I hear you asking. Luckily, we only need to do that for the schema generation - we can fully leverage the usual GraphQL flow for query documents that you send us, from the string you send us down all the way to the response JSON.
To make GraphQL work, there are two main parts you have to focus on:
One of the strengths of GraphQL is that it’s based on strongly-typed schema definitions. These type definitions define how the data should look like and what queries are actually allowed with your GraphQL API. A type definition looks as follows:
The definition above defines that the type
AssetFile has exactly two fields (
fileName), with both being type
String. The cool thing about that definition is now that we can use it inside of other type definitions.
The SDL makes it possible to define a complete data set:
When you use tools like GraphiQL, an in-browser IDE to explore GraphQL endpoints, you may have noticed that you can easily discover the data available at the API endpoint by opening the docs section. The docs section includes all information based on the schema that was written in the SDL you defined.
Sidenote: The folks from Prisma also built a tool called GraphQL Playground which sits on top of GraphiQL adding a few extra features and a "more up to date" UI
The way these GraphQL tools work is that they send one initial request on startup – a so-called
IntrospectionQuery, which is a standard GraphQL request that uses POST and includes a GraphQL query in the request payload. The requests performed by a GraphQL user can differ based on the use of different query types.
The response to this introspection query provides all the schema information that is needed to provide API documentation, make auto-completion possible, and give the client-side developer all the guidance to happily query whatever data she is interested in.
Now that we have defined the available data schema, what’s missing is the GraphQL request that includes a query document. The query document is the actual GraphQL query that you already saw at the beginning of this article.
The query document is basically a string value that’s included in the payload hitting our GraphQL endpoint. The tools GraphiQL and GraphQL Playground will help in writing your first queries easily.
So why are ASTs so important for GraphQL?
When a request hits our GraphQL endpoint, the schema written in SDL and the query document included in the request payload will be read and transformed into ASTs. If parsing succeeds, we can be sure that both the query and schema are valid; otherwise, we can display errors detailing where something is syntactically incorrect.
Then we visit each field name in the query document to check if a corresponding type definition is present in the schema and if they’re compatible—do they have the same amount of arguments and are these of the same types?
If these validations pass, we can proceed to respond to the request by resolving the resources requested in the query. Resolvers are a topic we won’t cover in this article but in case you’re interested, you can read Prisma’s introduction "GraphQL Server Basics: GraphQL Schemas, TypeDefs & Resolvers Explained"—it’s an excellent read!
GraphQL’s power lies in its schema and type definitions which move API development to a completely new level. Thanks to the rich ecosystem, the tools and the concept of abstract syntax trees, it’s good fun to develop our new GraphQL endpoint at Contentful.
Moreover, it’s not only about developer experience but rather about a whole set of new possibilities. With ASTs, you can easily transform the resulting schema definition—this is, for example, what makes schema stitching easily possible.
Schema stitching is the idea that you can take two or more GraphQL schemas, and merge them into a single endpoint that can pull data from all of them.
Think about that for a moment—with GraphQL, we can very easily combine several APIs into a single powerful one. Combine this with the power of serverless technologies and API development as you currently know it will be something of the past. Be ready! ;)
Learn more about getting started with GraphQL and Contentful. Start by creating a free Contentful account, if you don't already have one, and find out how effortless our content infrastructure works with your code and static site projects.
Sidenote: Nikolas Burg also gave an excellent presentation on how to do schema stitching using Contentful and Prisma at our previous Contentful meetup in Berlin. It’s worth watching!
If reading is not your jam, I also spoke about this exact topic at one of our Contentful User Meetups. Check it out here: