Rethinking REST Practices: An Introduction to GraphQL with AWS AppSync

Mat Warger Amazon Web Services, AWS, JavaScript, Programming, Technology Snapshot Leave a Comment

Eventually, you realize that API development is nothing more than list manipulation.

A Better Way to Think About Data

The REST is History

The basic premise of data transfer and involves requesting and receiving lists. This is simplistic, but it gets to the root of why we’ve developed the technologies and best practices to pass data using web services.

RESTful APIs have grown to serve the needs of numerous individuals, startups, and enterprise companies across the world. They are useful, productive, and the concepts surrounding them are relatively standardized. If you don’t know how to create one, you can quickly find information building a great API that can grow to fit your needs. That’s when things get complicated…

If you start digging into REST, you’ll realize there’s quite a bit more to throwing lists. There are common threads that many people encounter when developing an API, and you begin to encounter many of the same questions so many others have before, such as:

  • How strictly should you adhere to the principles of REST? How do you decide which ones count?
  • How should you handle versioning? Should you bother?
  • How do you want to structure your objects? What is the shape of the data that works best for the clients of your API?
  • Are you sending the appropriate data to your users? Are you sending them the information they don’t need?
  • Concerning related or hierarchical data, are they able to efficiently query for what they need from nested structures?
  • Are users able to easily figure out what API endpoints are available and how they should be used?

There are many ways to approach these. It boils down to communicating the structures that a given endpoint will return or accept. The cascade of questions that results from the choices made here will ripple through from the back-end to the client. The secondary issue is that these questions and choices are not at all uncommon. There are answers to these that follow Best Practices. But there is still plenty of ambiguity involved when attempting to build a flexible API that works well. These are the Commonly Tolerated Situations.

If you hadn’t already guessed, there is a solution that frees us from the dogma of REST and allows us to solve all these issues in a declarative, powerful, and fun way. That solution is GraphQL. In this blog, I’ll provide an introduction to the GraphQL specification with code examples. (Part two is here.)

Specification and Structure

GraphQL is a specification, first and foremost. It enables your data interactions to be declarative. The implementation of this spec entails creating a schema that describes the types of data (more concretely, the shape) that is exposed to the client of your API. It is not a replacement for a database, it is not an object-relational mapping system — it is a set of tools to replace (and as we’ll see later, augment) a traditional REST API. This can be used in combination with all the business layers and software tiers you may already use to interact with your data.

The interactions take the form of queries that are sent to what is traditionally a single GraphQL endpoint. You may often see this endpoint resemble something similar to www.mysite.com/graphql.

A client (your application) can send requests to the server that contain Queries (fetching data) and Mutations (manipulating data). A Query or Mutation is received by the GraphQL server and broken down into its constituent parts, and the data is resolved and sent back. It can be broken down because the GraphQL server uses a schema to know the different types of data it can resolve and which resolvers to use for those types.

See Also:  Creating a Slack Bot

Whoa! There’s a lot there. Let’s break this down, starting with the basics.

Building From Types

The basis of all the great abilities that are unlocked when using GraphQL come down to Types. This enables the structured nature of your API calls. This allows the server to intelligently return data that adheres to that structure. This allows the client to introspect that data to discover the structure that it is allowed to consume. This introspection provides for a development experience and tooling that is way beyond generated documentation (think Swagger) allowed by a traditional REST API.

Introspection, tooling, and lack of ceremony means less silliness, by default.

Many of the issues present in a REST API are either not possible or, at least, not easily reproduced. Many of the Commonly Tolerated Situations simply disappear. Let’s go deeper into these ideas by looking at some basic examples of types.

Here is an example of a basic type for a Game that includes a title, description, and rating.

type Game {
  title: String!
  description: String
  rating: Int!
}

This type declaration declares the types of the object that can be returned when referencing a game in a Query or Mutation. The title is declared as a String! — this means that the value should be a String and the exclamation point means that the value is non-nullable (aka required). This says that when the Game type is retrieved or created, this contract will be enforced and an error will be generated saying that the value doesn’t exist.

There are five scalar types in GraphQL, by default. These include String, Int, Float, Boolean, and ID. All types declared in the schema boil down to these (and in custom server implementations, you can implement your own). This means that all user-defined types, like our Game type above, must have resolvers provided so that the data can be gathered from a service, database, or other data source.

Resolvers

Resolvers are used to retrieve or manipulate the data defined by Types.

For example, our Game type listed above might have a resolver that queries a database for all games from a table. It might call a service that in turn calls a DAO layer that queries the table. It might even have a resolver that calls a REST API that returns a list of games. All a resolver should do is worry about the type of data it is defined to resolve. In this way, they can adapt any data source or service into one unified API.

Resolvers provide an adaptable API surface by resolving types from a service, DAO, or even another REST API.

This is key. The power of this type-based approach lets you think of your data more as buckets or lists, rather than relations between structures. The relations between types can be established in a loosely coupled possibility, not a strongly coupled outcome. We’ll come back to this distinction later.

Queries and Mutations

Queries and Mutations are the two types of interactions possible with a GraphQL implementation. Queries request data from the server. Mutations interact with data by causing side-effects and returning results. They depend on resolvers to do the operations on the data. They provide the API interface the client uses to interact with the data. Let’s look at some basics.

Queries

Let’s look at a basic example of a query to get a list of games using the Game type we defined earlier.

allGames {
  title
  description
  rating
}

This is the structure of a basic request that can be parsed by a GraphQL server. This tells the server to run the allGames query that is defined in the schema and return the title, description, and rating properties for each game. This uses the resolver defined by the schema for the allGames query to actually fetch data from a data source.

See Also:  Spring Boot Profiles: A Strategic Way to Configure Applications

Mutations

Here is an example of a basic mutation that creates a game.

createGame (
  title: String!, 
  description: String, 
  rating: Int! ) 
{
  title
  description
  rating
}

This example shows how to define a mutation that accepts title, description, and rating as variables. The resolver for this createGame mutation would be responsible for taking the variables and inserting them into a table, calling a service, or otherwise handling the operation that the mutation entails. This operation would complete and return the data that was inserted.

We will take a deeper look at queries and mutations in a little bit. Next, let’s discuss how all of these above concepts come together on the client and server.

Client & Server

Client

The client is a little out of scope for this article. Suffice it to say, it can range from a simple HTML page sending POST or GET requests to the use of a GraphQL client library such as Apollo. We will cover that in a future post…

Server

A GraphQL server is the backbone of your API. This is what receives requests and processes them to return data or perform mutations. This can be a custom implementation (Node, .Net, Java, etc…) or a hosted service instance (like AppSync or Graphcool).

With a traditional REST API, you might have a series of endpoints that define the data that can be requested by the client. When you send a request to a specific endpoint, the data you receive as a response or actions that are performed are defined ahead of time by the server.

This is in sharp contrast to a GraphQL endpoint, as any specific request you send is processed based on what is contained in that request. The server handles this by describing the possibility of the response, not the response itself. The client is responsible for requesting what it needs or the operations it wants to perform based on the Queries and Mutations that are allowed.

A Query doesn’t describe what is returned. Rather, it describes what can be returned. The contract is not a set of data, the contract is a shape of data.

The implementation you choose will be responsible for parsing these operations and reacting to them. Let’s take a look at what this means in practice by starting our own instance with AWS AppSync.

More to come in the next installment… stay tuned!

In Closing

I think for most cases, GraphQL is an objectively better alternative to RESTful APIs. Its flexibility shines most in its type system and the technology this enables. This lends to great tooling, a wonderful development experience, and a rapid iteration cycle that can’t be matched by traditional API development. We’ll soon check out AWS AppSync to see how this compares with a traditional REST API — follow me on twitter at @mwarger if you would like to know when the next article drops.

See a follow up to this post here!

For more on API development with GraphQL, check HowToGraphQL. If you have any questions or comments, please leave them below.

Editor’s Note: If you like this post, you won’t want to miss Mat Warger’s upcoming presentation at the Nebraska.Code() Conference on Friday, June 8th: Rethinking REST Practices with GraphQL and AWS AppSync.


About the Author
Mat Warger

Mat Warger

I'm a software consultant with Keyhole Software in Kansas City. I am a coder and life-long student, also enjoying technology, learning, cooking, and gaming. Recent projects have included React, Angular, and Java.


Share this Post

What Do You Think?