GraphQL combination & composition.

Ven Korolev
4 min readMay 30, 2016

Hi. I want to tell you about my experience working with GraphQL. This story is mostly for people who are familiar with it. But it will also be helpful for people who wants to play with it.

GraphQL has a good documentation(http://graphql.org/) and a perfect website(https://learngraphql.com/) for learning it.

Step 1. I skip initial setup(you can find it in official docs or github repos). So I want to start from a GraphQL server. There are not too many variations with declaration on a web server, but there are still some interesting things we can do.

app.use(‘/graphql’, graphqlHTTP((request) => {/* here we can get access to the request, we can use token or   session or whatever is has */  return {
schema,
graphiql: proccess.env.NODE_ENV !== 'production',
rootValue: {
id: 'viewer',
loaders: loaders,
files: request.files,
fetchBackend
},
};
};

Let me tell you more about our returned value. It has 3 fields:

  1. schema is just a schema which is required by graphQL. We have to return it.
  2. graphiql is a graphQL debugger which we turn on only if NODE_ENV doesnt’s equal ‘production’ which means that we use it only for developing. graphiql will be available on localhost:YOUR_PORT/graphql
  3. rootValue(moved to the fourth parameter in 0.9.0 and above versions) is a value which will be passed to each resolve method in our type’s fields. rootValue also has inner fields let’s discover what they are:
  • id is our global object’s id
  • loaders are our loaders for fetching data by id from our back-end(db, REST). We use facebook’s dataloader library.
  • files are files which we might pass through our server during saving or uploading them.
  • fetchBackend is our back-end fetch functions implementation. Yes we use REST API under graphQL.

Step 2. Now let’s talk about our webserver’s folder structure. We have 3 folders which contain different types of files.

-webserver
--mutations
--queries
--types
-index.js

Mutations folder contains only server-side mutations. Queries folder contains only queries. Type folder contains only our graphQL types. index.js describes our graphQL’s roots and compose types.

Let’s talk about them briefly. We skip graphQL type’s declaration or mutation’s declaration. We will talk only about how we can compose and link all of them.

Our index.js looks like this:

import * as types from './types';const {nodeInterface, nodeField} = nodeDefinitions((globalId, {rootValue}) => { 
return rootValue; //our rootValue from step1.
}, (obj) => {
return refs.viewer; //just return the global object every time.
});
const refs = Object.keys(types).reduce((acc, key) => {
acc[key] = types[key](acc);
const {connectionType, edgeType} = connectionDefinitions({
name: acc[key].name,
nodeType: acc[key],
});
acc[key + 'Connection'] = connectionType;
acc[key + 'Edge' = edgeType;
}, {nodeInterface, nodeField});
export default new GraphQLSchema({
query: new GraphQLObjectType({
name: 'rootQuery',
fields: () => ({
node: nodeField,
viewer: {
type: refs.viewer,
resolve: (root) => root
},
}),
}),
});

So, this piece of code requires explanation. First of all we import all of our types, we use an ES6 feature . To use this you have to have index.js file in a folder which contains all of files you want to export from. Example types/index.js: export { default as viewer } from ‘./viewer’.

The second part of code just describes how we resolve nodes. First function is responsible for returning an object(this is a good place for loaders), second for returning a type. You can read about it on the graphQL’s website.

And the last part and the most interesting. We use Object.keys function to get all keys from our types, then we reduce all keys and accumulate a new value. For each key we get a type’s function and call it passing an accumulated value as an argument. After that we generate connection and edge definitions and save it in our accumulated value as a new elements. As an initial accumulated value we’re passing an object with two objects already, nodeField and nodeInterface.

Step 3. How can we use it in our types, queries or mutations. We start from types. Each of our types is a function which accepts only 1 argument and it is refs. Let’s create our global viewer.

import * as queries from '../queries';export default (refs) => new GraphQLObjectType({
name: 'Viewer',
fields: () => ({
id: {type: new GraphQLNonNull(GraphQLID)},
...Object.keys(queries).reduce((acc, key) => {
acc[key] = queries[key](refs);
return acc;
}, {}),
}),
interface: [refs.nodeInterface],
});

Ok, here we declare new GraphQLObjectType, but we also compute our fields by using reduce. The point of this is to call each querier’s function(a query is a function as a type and a mutation are) and pass our refs(computed types) to it. Then we add a new value to an accumulated value and return it. We can use refs.nodeInterface for an interface field due to refs. But we also can use our refs to refer to another type:

export default (refs) => new GraphQLObjectType({
name: 'User',
fields: () => ({
name: {type: GraphQLString},
car: {type: refs.car}, // we refer to another type.
}),
});

Step4. Using refs in queries. Now we talk about refs in queries and the rootValue in resolve function.

export default (refs) => new GraphQLObjectType({
type: refs.user,
args: {
id: {type: GraphQLID},
},
resolve: async (rootValue, args) => {
const {fetchBackend, loaders} = rootValue;
const response = await fetchBackend('...');
const car = await loaders.Car.load(args.id);
...do smth
return await response.json();
},
});

You might have noticed how cool it is to have the rootValue in a resolve function. You do whatever you want. And you can pass whatever you want. But you have to be careful. With great power comes great responsibility.

Step 5. Let’s finish this story with mutations. It is really similar to queries but still has some nice things.

export default (refs) => mutationWithClientMutationId({
name: 'Upload',
inputFields: {},
outputFields: {
upload: {type: refs.upload},
},
resolve: async (args, {rootValue}) => {
const {fetchBackend, files} = rootValue;
const form = new FormData();

form.append('file', fs.createReadStream(files.upload.path), {
filename: files.upload.name
});

const response = await fetchBackend('...', form);

return await response.json();
},
});

This is how we store files in GraphQL. Looks very similar to queries.

Next time i will tell you more about GraphQL and how we can integrate it with Relay. Thank you for your attention and don’t hesitate to leave a comment or a like.

--

--

Ven Korolev

I am a javascript preacher. Write about React, Relay, GraphQL, Testing.