Serverless GraphQL Architecture With Graphcool

Today, new application development leans towards microservices and serverless approaches. With this paradigm shift, the weaknesses of traditional RESTful API approaches began to show. GraphQL was developed in response to the problems of a typical REST API, but requires a fair amount of configuration to get it running server-side. According to Nikolas Burk, full-stack developer, Graphcool brings together the concepts of GraphQL and serverless, allowing for the simple creation of application backends.

This article will explore why GraphQL is a great alternative to REST, how serverless functions work today, and how Graphcool is used for backend development. Read all the way to the end for a practical demo and some Graphcool code.

Watch Nikolas Burk’s presentation on Graphcool (Slides here):

GraphQL – The Better REST

GraphQL is an open source API standard, created by Facebook and released in 2015. The QL at the end of the name stands for ‘Query Language’, but despite common misconception, GraphQL has nothing to do with databases. Rather, it’s a query language for APIs. Essentially, it offers users a declarative method of interacting with the data, making it easy for clients to get the data they need and for APIs to evolve with time.

For an introduction to GraphQL read: 5 Potential Benefits of Integrating GraphQL

The Blogging App

With typical REST, acquiring data to populate a simple app can easily lead to over-fetching. Repurposed from Nikolas Burk’s slide.

To fully convey the benefits of GraphQL, Burk utilizes a practical example. He presents a Medium-style blogging application, focusing on the profile screen for a given user. On this profile, some standard content is rendered: the username, their 3 latest posts, and their 3 most recent followers.

Rendering this profile screen with a typical REST API requires a decent amount of code. Each portion of data requires a separate API call, and brings with it a predetermined selection of data. Fetching the username, for example, might return a JSON payload containing the username, their full name, their email address, and their signup date, when all that’s needed is the username. Between the multiple API calls and the various bits of unwanted data, the inefficiencies of a traditional REST API are clear.

GraphQL takes a different approach. Rather than having multiple endpoints, there is a single endpoint which both accepts and returns a flexible data structure. The query specifies exactly which data is needed from the server. GraphQL then works on the backend to return exactly the requested information in the prescribed structure. A single API call like the one below will return all the necessary data, a boon for application performance and mobile data plans.

query {
User(id: "er3tg439frjw") {
Name
posts {
 Title
 }
 followers(last:3) {
 Name
 }
}
}

The GraphQL Schema

The Schema is the core of GraphQL. Schema serves the important purpose of defining the API. It lays out the different data types, and the various methods of interacting with the API for any given CRUD application. As Burk puts it, “Schema really defines the functionality of your API and thus is the contract for client-server communication.”

Practically speaking, the Schema is written in Schema Definition Language (SDL). The Schema is organized by types and fields (rather than endpoints like a REST API) allowing for flexible data access via a single endpoint. There are 3 root types in SDL which define the API access – query, mutation, and subscription. Query reads data, mutation changes data, and subscription allows the client to monitor data for changes, firing pre-determined events on change.

Going Serverless – Monoliths vs. Microservices

The importance of GraphQL is more clear with an understanding of the monolith style of software development versus microservices. Monoliths enjoyed historical popularity, but microservices are better suited to the new style of software development. Paired with GraphQL, microservices offer many advantages to developers.

Monolith Software Design

Monolithic architectures have a long history in software development, and brought it to where it is today. Frankly, they worked well for a long time. They have a lot of advantages, like a simple team structure, meaning less communication overhead. Global type safety is also guaranteed when all the application components are built together in one system, and the data is universally understood.

On the other hand, monoliths have a number of weaknesses exacerbated by the pace of agile software development. Because they are so self-contained, unit testing is a nightmare, and scalability is tough. Deployment cycles tend to be long thanks to all of the interdependencies, making continuous integration impossible. This makes it hard to respond to customer needs in a timely fashion, resulting in stagnation.

Microservices

Microservices came about as a structural solution to the monolithic architecture problems, and are widely used today. They offer many advantages, which directly address the weaknesses of monoliths. Microservices are easy to test, because engineers only need to write unit tests for small, self-contained components. Continuous integration and deployment are a breeze, thanks to the ability to deploy just one microservice at a time.

While microservices managed to solve many existing problems, they brought about new challenges of their own. Communication and organization are exponentially more difficult with different teams handling each microservice. Orchestrating the interplay of services is another challenge Burk points out. While there are frameworks to help, it’s something teams must grapple with regularly. There is also the data and statefulness of various components to consider. Questions like where is the database, who is able to access it, and how is data kept in sync regularly challenge engineering teams.

Event Driven Architecture

One of the major challenges in the microservice design is how different microservices communicate. This challenge is what spawned event driven architecture, which “really is a very good idea and largely accepted idea of how you should ,” according to Burk.

Event driven architecture specifies what happens when two microservices need to communicate. This communication takes place through a PubSub system. One microservice subscribes to another microservice, listening for the occurrence of a particular event. When any event takes place, the subscribing microservice is notified and can execute any desired functionality.

Serverless Functions – The Future of Serverless

Burk points out that Functions as a Service (FaaS) are the natural next step in the evolution of serverless applications. Rather than continuing to deploy services as entire web servers, engineers are starting to deploy individual functions. These functions handle one tiny piece of functionality, and are completely self-contained.

Producing these FaaS is straightforward. A source file is created which contains just a single function. This function is then deployed and integrated into the system. When it is needed, the function is invoked via an HTTP webhook, just like a traditional server, but no server configuration is required. There are many services available to aid in the deployment of FaaS, including AWS Lambda, Microsoft Azure, and Google Cloud Functions. These services do a ton of the heavy lifting, things like resiliency and scalability, leaving time to focus on building real features.

Introducing the Graphcool Framework

Graphcool is a relatively new framework which provides a serverless GraphQL backend. It “is effectively abstracting away your database and automatically generating a CRUD API for you,” says Burk. This means that there’s no more dealing with SQL, database queries or database schema. The data model is defined with SDL and it’s all automated from there. Long story short, it could be easier to start using GraphQL when you have Graphcool to handle the backend.

Another advantage of using Graphcool is the event-driven core, used to implement business logic. This event architecture uses a resolver-pattern, making it a great fit for modern, serverless software. Graphcool also provides a global type system which is determined automatically by the GraphQL Schema. With this system, communication between components is safer and easier, bringing back an advantage of the monolith architecture.

Using Graphcool to Build Serverless GraphQL Backends

Graphcool makes it easy to utilize serverless functions. These serverless functions are leveraged through three function types:

  • Hooks allow the client to do synchronous data validation and transformation. Hooks are like auto-generated GraphQL queries and mutations.
  • Subscriptions provide the ability to trigger asynchronous events.
  • Resolvers are methods which extend the Graphcool framework with custom GraphQL queries & mutations.

With these function types, the Graphcool backend can handle a lot thrown at it. The Graphcool team has produced building blocks for a lot of standard backend functionality, like creating users, subscriptions, and setting permissions. These can be copied into an existing Graphcool setup, tweaked for the application, and put right to work. To fully understand how Graphcool might work with different systems, Burk explores some practical examples.

Subscription Event – The Theory

Take a system which needs to send a welcome email to new users. The first step is to send a subscription query to the event system. This query allows the system to monitor constantly and await any event which is related to the Person type. The query might take a form similar to this, specifying the query type (subscription) and the type to monitor (Person):

subscription {
    Person {
   	 node {
   		 name
   		 email
   	 }
    }
}

At this point, the system is actively monitoring for any person events. At some point, the client application will create a new person using some GraphQL code to declare the query type (mutation), the name of the mutation (createPerson), the provided data from the user (name & email), and the data to return (id).

mutation {
    createPerson(
   	 name: "Nikolas"
   	 email: "nikolas@graph.cool"
    ){
   	 id
    }
}

When this mutation is received by the server, this causes the event system to fire on that subscription query. A payload containing the requested data is then sent back to the server.

"data": {
    "Message": {
   	 "node": {
   		 "name": "Nikolas",
   		 "email": "nikolas@graph.cool"
   	 }
    }
}

This payload triggers the function specified in the original subscription query. The function then executes the required code to do the actual sending of the email, in the preferred language of the API developer.

Subscription Event – Implementing in Graphcool

That’s the subscription event in theory, but how is it actually built with Graphcool? To start, it requires a file structure similar to this:

├── graphcool.yml
├── types.graphql
├── src
│   ├── welcomeEmail.graphql
│   ├── welcomeEmail.js

The types.graphql file is used for exactly what the name implies – declaring the different data types within the application Schema. In this example only one type is needed, the Person type. As a note, the ! Indicates a field being required. The email field is not required to create a new Person.

type Person {
    id: ID! @isUnique
    createdAt: DateTime!
    updatedAt: DateTime!
    name: String!
    email: String
}

Besides the types file, the only other required file is graphcool.yml. This YAML file is the Graphcool config file. It imports the types file, sets up various functions, handles application permissions and more. A very basic file will suffice to get the demo up and running:

# Type Definitions
types: ./types.graphql
''
# Permission Rules
permissions:
# for simplicity, open access
- operation: "*"
''
# TODO: Function Definitions
# functions:

At this point, the Graphcool backend is usable. To deploy and generate an automated CRUD API, the Graphcool CLI is needed. First, install the tools via NPM:

npm install -g graphcool

Then simply navigate to the application directory in the terminal, and deploy the project:

graphcool deploy

This command does a couple of things. It generates the CRUD API for this application. It then deploys the service to a local docker cluster, with the Person type accessible via API. To experiment with the API as it currently exists, the Graphcool playground is available. It’s a sandbox, similar to tools like Postman, where the API can be utilized to both query and mutate application data in an interactive format. To get the URL for the application profile, use the command:

graphcool info

Grab the Simple API Endpoint url, and open it in a browser. This opens up the interactive playground, where the API can be explored. For example, to see all Persons in the application database, use a query like this, and hit the play button.

{
    allPersons {
   	 id
   	 name
    }
}

This query would return a payload of data containing the user ID & name for all Persons created in this application. Of course, no users are created yet, indicated by the empty array it currently returns. To add a Person, open a new tab in the playground to compose a mutation query. Executing this query will add a new person to the application with the specified name:

mutation {
    createPerson(name: "Sarah") {
   	 id
    }
}

After executing this query, running the allPersons query again will return an array containing the Person with the name of Sarah. The great part about Graphcool is that all of this functionality is automatically created upon deploying the basic config and types file.

In the Graphcool API, a standard naming convention is followed. Every type created has CRUD endpoints generated automatically. The Person type, for example, has the following API endpoints – create (createPerson), read (allPersons), update (updatePerson), and delete (deletePerson).

At this point, it’s necessary to set up the intended application functionality, to send all new Persons an email when their account is created.

The code to actually send the email is composed in welcomeEmail.js, which exports a single function. This function accepts one argument, containing the event data from Graphcool. Within the function, the submitted data (e.g. the user name and email address) is accessible via event.data.Person.node.fieldName. Actually sending the email can be done via any preferred method, such as a transactional email service like Mailgun.

module.exports = event => {
    // access the user information
    const name = event.data.Person.node.name;
    const email = event.data.Person.node.email;
''
    // place code here to send the email via the preferred method
}

Next, the subscription Schema must be declared. This is done in the welcomeEmail.graphql file. This declaration is fairly straight-forward, declaring the query type (subscription), and specifying the Schema type to monitor (Person). The node object contains the fields which should be passed in through the event data.

subscription {
    Person(filter: {
   	 mutation_in: [CREATED]
    }) {
   	 node {
   		 name
   		 email
   	 }
    }
}

One item of note is the filter attribute on the Person type declaration. This filter specifies that the subscription should only fire when a new Person is created. By default, a subscription will monitor for any Person modifications, whether it is creating, updating or deleting.

The final step is to declare the function within the Graphcool config file, graphcool.yml. Functions get their own section of the config file, and within the declaration the handler code, query Schema, and the function type are all specified.

functions:
    welcomeEmail:
   	 handler:
   		 code:
   			 src: ./src/welcomeEmail.js
   	 type: subscription
   	 query: ./src/welcomeEmail.graphql

Once this is set up, the process is just like before. The code is compiled and the API is generated by running the graphcool deploy command. From there, navigate to the playground again. Running the createPerson query, specifying the name AND email address, will trigger this subscription function and send the email.

mutation {
    createPerson(name: "Sarah", email: "sarah@gmail.com") {
   	 id
    }
}

Graphcool has uses far beyond the scope of this demonstration. It can wrap existing REST APIs to create a single point of entry for all client services, even if direct database access isn’t possible. Pre-made functionality is available to assist with creating users, login flows, and data validation. These are just a few of the things possible with Graphcool. To set up a fast, easy, and serverless GraphQL backend, Graphcool is a framework worth considering.

Slides