The GraphQL schema

The schema is the vocabulary used by GraphQL backend server, and the Relay components in the frontend. The GraphQL type system enables the schema to describe the data that's available, and how to put it all together when a query request comes in. This is what makes the whole approach so scalable, the fact that the GraphQL runtime figures out how to put data together. All we need to supply are functions that tell GraphQL where the data is. For example, in a database or in some remote service endpoint.

Let's take a look at the types used in the GraphQL schema for the TodoMVC app:

import { 
  GraphQLBoolean, 
  GraphQLID, 
  GraphQLInt, 
  GraphQLList, 
  GraphQLNonNull, 
  GraphQLObjectType, 
  GraphQLSchema, 
  GraphQLString, 
} from 'graphql'; 
 
import { 
  connectionArgs, 
  connectionDefinitions, 
  connectionFromArray, 
  cursorForObjectInConnection, 
  fromGlobalId, 
  globalIdField, 
  mutationWithClientMutationId, 
  nodeDefinitions, 
  toGlobalId, 
} from 'graphql-relay'; 
 
import { 
  Todo, 
  User, 
  addTodo, 
  changeTodoStatus, 
  getTodo, 
  getTodos, 
  getUser, 
  getViewer, 
  markAllTodos, 
  removeCompletedTodos, 
  removeTodo, 
  renameTodo, 
} from './database'; 
 
const { nodeInterface, nodeField } = nodeDefinitions( 
  (globalId) => { 
    const { type, id } = fromGlobalId(globalId); 
 
    if (type === 'Todo') { 
      return getTodo(id); 
    } else if (type === 'User') { 
      return getUser(id); 
    } 
 
    return null; 
  }, 
  (obj) => { 
    if (obj instanceof Todo) { 
      return GraphQLTodo; 
    } else if (obj instanceof User) { 
      return GraphQLUser; 
    } 
 
    return null; 
  } 
); 
 
const GraphQLTodo = new GraphQLObjectType({ 
  name: 'Todo', 
  fields: { 
    id: globalIdField('Todo'), 
    text: { 
      type: GraphQLString, 
      resolve: ({ text }) => text, 
    }, 
    complete: { 
      type: GraphQLBoolean, 
      resolve: ({ complete }) => complete, 
    }, 
  }, 
  interfaces: [nodeInterface], 
}); 
 
const { 
  connectionType: TodosConnection, 
  edgeType: GraphQLTodoEdge, 
} = connectionDefinitions({ 
  name: 'Todo', 
  nodeType: GraphQLTodo, 
}); 
 
const GraphQLUser = new GraphQLObjectType({ 
  name: 'User', 
  fields: { 
    id: globalIdField('User'), 
    todos: { 
      type: TodosConnection, 
      args: { 
        status: { 
          type: GraphQLString, 
          defaultValue: 'any', 
        }, 
        ...connectionArgs, 
      }, 
      resolve: (obj, { status, ...args }) => 
        connectionFromArray(getTodos(status), args), 
    }, 
    totalCount: { 
      type: GraphQLInt, 
      resolve: () => getTodos().length, 
    }, 
    completedCount: { 
      type: GraphQLInt, 
      resolve: () => getTodos('completed').length, 
    }, 
  }, 
  interfaces: [nodeInterface], 
}); 
 
const Root = new GraphQLObjectType({ 
  name: 'Root', 
  fields: { 
    viewer: { 
      type: GraphQLUser, 
      resolve: () => getViewer(), 
    }, 
    node: nodeField, 
  }, 
}); 

There are a lot of things being imported here, so we'll start with the imports. I wanted to include all of these imports because I think they're contextually relevant for this discussion. First, we have the primitive GraphQL types from the the graphql library. Next, we have a bunch of helpers from the graphql-relay library that simplify defining a GraphQL schema. Lastly, we have imports from our own database module. This isn't necessarily a database, in fact in this case, it's just mock data. We could replace database with api for instance, if we needed to talk to remote API endpoints, or we could combine the two; it's all GraphQL as far as our React components are concerned.

Then, we define some of our own GraphQL types. For example, the GraphQLTodo type has two fields—text and complete. One is a Boolean and one is a string. The important thing to note about these fields is the resolve() function. This is how we tell the GraphQL runtime how to populate these fields when they're required. These two fields simply return property values.

Then, there's the GraphQLUser type. This field represents the user's entire universe within the UI, hence the name. The todos field, for example, is how we query for todo items from Relay components. It's resolved using the connectionFromArray() function, which is a shortcut that removes the need for more verbose field definitions. Then, there's the Root type. This has a single viewer field that's used as the root of all queries.

Now, let's take a peek at the add todo mutation. We're not going to go over every mutation that's used by the web version of this app, in the interest of space:

const GraphQLAddTodoMutation = mutationWithClientMutationId({ 
  name: 'AddTodo', 
 
  inputFields: { 
    text: { type: new GraphQLNonNull(GraphQLString) }, 
  }, 
 
  outputFields: { 
    todoEdge: { 
      type: GraphQLTodoEdge, 
      resolve: ({ localTodoId }) => { 
        const todo = getTodo(localTodoId); 
 
        return { 
          cursor: cursorForObjectInConnection( 
            getTodos(), 
            Todo 
          ), 
          node: todo, 
        }; 
      }, 
    }, 
    viewer: { 
      type: GraphQLUser, 
      resolve: () => getViewer(), 
    }, 
  }, 
 
  mutateAndGetPayload: ({ text }) => { 
    const localTodoId = addTodo(text); 
    return { localTodoId }; 
  }, 
}); 
 
const Mutation = new GraphQLObjectType({ 
  name: 'Mutation', 
  fields: { 
    addTodo: GraphQLAddTodoMutation, 
    ... 
  }, 
}); 

All mutations have a mutateAndGetPayload() method, which is how the mutation actually makes a call to some external service to change the data. The returned payload can be the changed entity, but it can also include data that's changed as a side effect. This is where the outputFields come into play. This is the information that's handed back to Relay in the browser so that it has enough information to properly update components based on the side effects of the mutation. Don't worry, we'll see what this looks like from Relay's perspective in a little bit.

The mutation type that we've created here is used to hold all application mutations; obviously, we've omitted some of them here. Lastly, here's how the entire schema is put together and exported from the module:

export const schema = new GraphQLSchema({ 
  query: Root, 
  mutation: Mutation, 
}); 

We won't worry about how this schema is fed into the GraphQL server for now. If you're interested in how this works, take a look at server.js.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.34.178