Practical GraphQL For RelayUnderstanding the SpecSibelius Seraphini
Sibelius Seraphini
@sibelius
@sseraphini
Abstract Engineer

Overview

  • Core assumptions
  • Object Identification
  • Connections
  • Mutations
  • Pratical GraphQL tips

Core assumptions

  • A mechanism for refetching an object.
  • A description of how to page through connections.
  • Structure around mutations to make them predictable.

Object Identification

{
node(id: "User:4") {
... on User {
name
}
}
}

Object Identification

interface Node {
"""The id of the object."""
id: ID!
}

Object Identification

  • Node Interface that has a non-null ID
  • Global unique ID per node
  • GlobalId = base64(type:id)
  • All nodes that implements NodeInterface are refetchable

Node Interface

import { fromGlobalId, nodeDefinitions } from 'graphql-relay';
import { GraphQLObjectType } from 'graphql';
import * as loaders from '../loader';
const registeredTypes = {};
export function registerType(type: GraphQLObjectType) {
registeredTypes[type.name] = type;
return type;
}
export const { nodeField, nodeInterface } = nodeDefinitions(
(globalId, context) => {
const { type, id } = fromGlobalId(globalId);
const loader = (loaders)[`${type}Loader`];
return (loader && loader.load(context, id)) || null;
},
object => registeredTypes[object.constructor.name] || null,
);

Connections

{
users(
first: $first,
after: $after,
before: $before,
last: $last
) {
edges {
cursor
node {
id
name
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}

What is a Cursor?

A cursor is a pointer to a "list"/"connection", it tells you where you are in a list of items

Forward Pagination

first: 10, after: "mycurrentcursor"

This tell GraphQL to return 10 items after "mycurrentcursor" cursor

Backward Pagination

last: 10, before: "anothercursor"

This tell GraphQL to return 10 items before "anotercursor" cursor

Mutations

  • It should return all necessary data to update your GraphQL client

Mutations that add a new Node/Edge to the Graph

  • It should return the new created edge to be add to existing connections

Mutation Add Edge

mutation ShipAddMutation($input: ShipAddInput!) {
ShipAdd(input: $input) {
shipEdge {
node {
name
}
}
}
}

Mutations that edit an existing Node

  • It should return the edited node

Mutation Edit Node

mutation ShipEditMutation($input: ShipEditInput!) {
ShipEdit(input: $input) {
ship {
name
}
}
}

Mutation that remove a Node

  • It should return the id of removed node

Mutation Remove Node

mutation ShipRemoveMutation($input: ShipRemoveInput!) {
ShipRemove(input: $input) {
deletedNodeId
}
}

Pratical GraphQL Tips

This is based on our experience working with GraphQL since their public release at 2015

Avoid Viewer

  • Viewer was a "hacky" solution for Relay Classic where you couldn't do query on QueryType
  • We recommend use fields directly on QueryType instead, so we can avoid confusion

Keep Logged User on GraphQL Context

  • As soon as you know who is logged user, add it to your GraphQL context so you can easily access them in any resolver
  • This is also useful to implement viewer can see (security) solutions on resolver based level

Add a me field resolver

  • me field on QueryType that resolves to a UserType of the logged user
  • This makes it easy to find out who is logged in your app

Each Node should have a load function

  • Each type should have an unique way to be resolved
  • This will make sure that every resolver is always resolving the correct data
  • This also make sure it is easy to add dataloader later on
  • UserLoader.load(context, id)

Create a Dataloader generator per database/resource

  • Check GraphQL Mongoose Loader
  • This make sure all data is resolved in the same way with all optimizations

Create one Connection per database/resource cursor

  • Check GraphQL Mongoose Connection
  • This make sure all your connections handle cursor and pagination properly

Follow Relay Mutation Input/Output Object pattern

  • Check graphql-relay
  • With an Input Object type in your mutation it is easy to add more fields without breaking old clients
  • The same goes to Output Object type

References

  • Relay Docs about GraphQL Server Specification
  • Object Identification
  • Cursor Connections
  • Relay Mutations

References

  • Relay Mutations Guide
  • Entria Playground
  • GraphQL Relay 2015
  • Building the New Facebook
  • GraphQL Mongoose