Standard service - a fixed amount of work that is performed at a fixed price.  Post Service

  

Monday, 06 May 2019 15:29

GraphQL: Queries, Mutations, Subscriptions

Written by  https://labs.cx.sap.com/2019/05/07/graphql-queries-mutations-subscriptions/
Rate this item
(0 votes)

I hope you enjoyed the last blog post of this series about GraphQL, which should have introduced the basics to you. In this post, I’ll cover Queries, Mutations

and Subscriptions. These are the three root types of a GraphQL schema and it’s essentially all you need to know to write any GraphQL query :-) At the same time, we’ll be exploring how to implement the server-side of these queries. Instead of going against a live system, which would require a lot of networking code to type, we’ll mock these responses. We’ll first have to setup a little demo GraphQL server, for which I’ve chosen ‘yoga‘.

Note: this will be a pretty practical post. If you want to follow along, get your Terminal ready and have node.js installed!

Understanding Queries, Mutations and Subscriptions

Each GraphQL schema has exactly three root types: query, mutation and subscription. Each request against the GraphQL endpoint of a GraphQL server (remember, always a ‘POST’) will need to have a payload starting with one of these, e.g.:

query { product(id:"123") { id name } }

As you can probably infer, the query above is asking for a product and will select just the id and name fields of it. Queries are used to request data from the server and they are the default GraphQL requests. Let’s take a look at the schema definition that fits this query:

type Query { product(id: String) : Product } type Product { id: ID name: String price: String reviews: [Review] } type Review { rating: Int text: String author: String } 

As you can see, we can ask for products via the id input parameter and the Product type is defined by having a few basic fields such as id and name. The reviews field of Product is special as it may contain a list of Review objects, that we’ll later resolve via the Product resolver. Good for now.

Besides queries, there are mutations and subscriptions. Mutations are used, whenever the server state is changed. Below is a mutation, that will signup a user (simplified), therefore create a new user object in a database (or similar).

mutation { signup(input:{email:"This email address is being protected from spambots. You need JavaScript enabled to view it.", password:"test1234"}) { token user { id email date } } }

Just as queries, mutations can return data and you have to select what data you really want. Here, we’ll expect a token for the newly signed up user and some information about the user just created. This is the schema that fits the above mutation:

type Mutation { signup(input: SignupInput): AuthPayload } input SignupInput { email: String! password: String! } type AuthPayload { token: String user: User } type User { id: ID email: String date : String }

When you look closely, the input parameters into the signup mutation have been combined into a single input using the ‘SignupInput’. We could as well have used individual parameters as we’ve done so for the product query, but it might be beneficial to combine inputs in case you want to reuse these. Our return data is defined via the AuthPayload type and includes a token as well as a user object.

The final root type to understand are subscriptions. A subscription is used to register for updates from the server. For example, we might want to be notified of new deals and could request these using this subscription:

subscription { deal { description } }

Our subscription is a pretty basic one – no input parameters and we’re just asking for the description field. Still: whenever a new deal arrives, we’ll now be notified. The exact implementation is up to the GraphQL Server, but typically websockets are used for this. This of course requires special support from the client side. Below is the schema that fits to our subscription:

type Subscription { deal: Deal } type Deal { description: String }

Creating a fresh yoga project

My goal for this part of the series is to really get you started with GraphQL./ So we will implement a basic GraphQL server and implement resolvers for the above schema. Instaead of going against live backend APIs, we’ll simply mock responses. Yet, you will definitely understand GraphQL way better and especially from the server side! We’ll start really from scratch, so change into your favorite dev directory and run:

mkdir myyoga cd myyoga npm init yarn add graphql-yoga

Let’s add the index.js file which will be the app’s main .js file for our app.

const { GraphQLServer, PubSub } = require('graphql-yoga') const Query = require('./resolvers/query'); const Mutation = require('./resolvers/mutation'); const Subscription = require('./resolvers/subscription') const resolvers = { Query, Mutation, Subscription } const pubsub = new PubSub() const server = new GraphQLServer({ typeDefs: './schema.graphql', resolvers, context: { pubsub } }) server.start(() => console.log(`Server is running on http://localhost:4000`))

As you can see from the code, we’re pulling in a graphql.schema file and we’re referencing the javascript modules, which combined are representing all our resolvers so far.

Let’s first create the schema.graphql file which holds all schema information for our GraphQL server. It’s essentially all the above schema snippets combined into a single file – schema.graphql:

type Query { product(id: String) : Product } type Mutation { signup(input: SignupInput): AuthPayload } type Subscription { deal: Deal } type Deal { description: String } type Product { id: ID name: String price: String reviews: [Review] } type Review { rating: Int text: String author: String } type AuthPayload { token: String user: User } input SignupInput { email: String! password: String! } type User { id: ID email: String date : String }

Next, we need to create our resolvers. While we could combine them all into a single file, I find it more realistic to spread these across several files. So let’s create a resolvers directory and then create three files: resolvers/query.js, resolvers/mutation.js and resolvers/subscription.js.

//resolvers/query.js function product(parent, {id}) { return { id : id, name : `Product ${id}`, price : Math.floor(Math.random() * Math.floor(100)) } } module.exports = { product } 

As we have a single query (check the schema), the ‘product’ query, we have to implement and export a single function here. The parameters passed into the function are the parent type (which here will be null, as we’re operating on the root element already) and the parameters. Just as promised, we’re returning a fake response and simply use the provided ‘id’ to make up some of the response data.

//resolvers/mutation.js function signup(parent, {input}) { return { token : "supersecrettoken", user : { id : "25", email : input.email, date : new Date() } } } module.exports = { signup, }

For our signup mutation, we’re again mocking the response. Interestingly, the input type that we used is ending up to be a single input object, so we can call ‘input.email’ to request the email address of the input object provided. Other than that, it’s just as with queries.

//resolvers/subscription.js const deal = { subscribe: (parent, args, { pubsub }) => { const channel = Math.random().toString(36).substring(2, 15) // random channel name setInterval(() => pubsub.publish(channel, { deal: {description: "New deal!" }}), 2000) return pubsub.asyncIterator(channel) }, } module.exports = { deal, }

Now subscriptions, which is a bit different. For each subscription, we have to provide an object containing a ‘subscribe’ function. This function is called by the GraphQL server and will provide the parent and argument parameters. As a third parameter, we’re now accessing the pubsub object that we’ve created in index.js and have carefully made part of the context – it will be provided to each resolver call. For our simplistic implementation, we’re creating an interval that will respond with a new ‘Deal’ every two seconds.

Congratulations! While it’s simplistic, you should now have created a simple GraphQL server and are ready to run it via:

yarn start

Navigate to localhost:4000 and then try out the sample queries we have explained at the beginning of the post, e.g. try this:

subscription { deal { description } }

If it all works well, you’ll see the GraphQL server responding every two seconds with a new deal.

Every field of a type can be a resolver

In probably about 30 minutes, you’ve created a basic GraphQL server including queries, mutations and even subscriptions. No rocket science, but if you’ve followed along, you will definitely have a better understanding now. There’s one thing I wanted to add – “every field of a type can be a resolver”! That’s one important thing that I did not immediately get when I looked at the simple getting started examples.

To show you what this means, let’s take a closer look at the Product type in our graphql.schema:

type Product { id: ID name: String price: String reviews: [Review] } type Review { rating: Int text: String author: String } 

As you can see, a product can be linked to a couple of reviews. But so far, our resolver for the product query does not return any reviews! Let’s fix it:

//resolvers/product.js function reviews(parent) { return [ {author: "Sven", rating: 5, text: "so cool..."}, {author: "Andreas", rating: 4, text: "yeah!"} ] } module.exports = { reviews }

First create the product.js resolver above. As you can see, it will include a single exported function ‘reviews’ which is the field of Product that we currently cannot cover.

Update the index.js and add the Product resolver:

const { GraphQLServer, PubSub } = require('graphql-yoga') const Query = require('./resolvers/query'); const Mutation = require('./resolvers/mutation'); const Subscription = require('./resolvers/subscription') const Product = require('./resolvers/product') const resolvers = { Query, Mutation, Subscription, Product } const pubsub = new PubSub() const server = new GraphQLServer({ typeDefs: './schema.graphql', resolvers, context: { pubsub } }) server.start(() => console.log(`Server is running on http://localhost:4000`)) 

Now start up the server and issue this query:

query { product(id:"5") { id name reviews { rating author } } }

It will now respond with reviews:

{ "data": { "product": { "id": "5", "name": "Product 5", "reviews": [ { "rating": 5, "author": "Sven" }, { "rating": 4, "author": "Andreas" } ] } } }

Hope you enjoyed it! Please let me know what you think in the comments or follow on Twitter. Also, next up we’ll be discussing how we can implement a simple but extensible GraphQL server as part of Kyma. We’ll also discuss how GraphQL as an extension/component of Kyma can be used to aggregate and combine enterprise application APIs such as teh SAP C/4 Hana suite APIs.

Read 162 times

Leave a comment

Make sure you enter all the required information, indicated by an asterisk (*). HTML code is not allowed.