GraphQL is coming

Artem Malyshev

@proofit404

django/channels

django/daphne

django/asgi_redis

proofit404/asgi_rabbitmq

pika/pika

jupyter/kernel_gateway

proofit404/dependencies

xquery-mode/xquery-mode

bbatsov/projectile

melpa/melpa


What's wrong with REST?

Expand relations

{
    "title": "Finish monthly growth report",
    "category": "Growth Team",
    "tasks": [
        {"order": 1, "title": "Review samples"},
        {"order": 2, "title": "Summarize"},
        {"order": 3, "title": "Send to customer"},
        ...
    ],
}

Custom endpoints

/todos_with_everything_i_need

/todos_with_everything_i_need_with_author_v2

/todos_with_everything_i_need_for_samsung_smart_tv

/tightly_coupled_endpoint_for_a_specific_client

GraphQL

Hello world

{
  me {
    name
  }
}

Hello world

           {
{            "data": {
  me {          "me": {
    name           "name": "Artem"
  }             }
}            }
           }

Schema

type Task {
  id: ID!
  title: String!
  description: String!
  createdBy: Employee!
  assignedTo: Employee!
  statusHistory: [Status]
  comments: [Comment]
}

Graphene

Schema in Python

type Task {                class Task(ObjectType):
  id: ID!                      id = NonNull(ID)
  title: String!               title = NonNull(String)
  description: String!         description = NonNull(String)
  created: Employee!           created = NonNull(Employee)
  assigned: Employee!          assigned = NonNull(Employee)
  statusHistory: [Status]      status_history = List(Status)
  comments: [Comment]          comments = List(Comment)
}

Schema in Django

class Task(DjangoObjectType):
    class Meta:
        model = TaskModel

class Query(ObjectType):
    tasks = List(Task)

    def resolve_tasks(self, args, context, info):
        return TaskModel.objects.all()

Mutation in Django

class CreateTask(Mutation):
    class Input:
        ...
    task = Field(Task)

    @staticmethod
    def mutate(root, args, context, info):
        ...

class Mutation(ObjectType):

    create_task = CreateTask.Field()

Views

schema = graphene.Schema(Query, Mutation)

view = GraphQLView.as_view(schema, graphiql=True)

urlpatterns = [url(r'^$', view, name='graphql')]

Authorization

class Query(ObjectType):

    tasks = List(Task)

    def resolve_tasks(self, args, context, info):

        if context.user.is_authenticated():
            return TaskModel.objects.all()
        else:
            return TaskModel.objects.none()

Filtration

class Task(DjangoObjectType):
    class Meta:
        model = TaskModel
        filter_fields = {
            'title': ['icontains', 'startswith'],
            'description': ['exact'],
        }

class TaskQuery(AbstractType):
    all_tasks = DjangoFilterConnectionField(Task)

Filtration

{
  allTasks(title_Startswith: "to") {
    edges {
      node {
        id
        title
      }
    }
  }
}

Batch processing

&

Server Cache

SQL for one GraphQL query

SELECT ... WHERE "employee"."id" = 2;
SELECT ... WHERE "employee"."id" = 3;
SELECT ... WHERE "employee"."id" = 3;
SELECT ... WHERE "employee"."id" = 2;
SELECT ... WHERE "employee"."id" = 2;
SELECT ... WHERE "employee"."id" = 2;
SELECT ... WHERE "employee"."id" = 4;
SELECT ... WHERE "employee"."id" = 5;
SELECT ... WHERE "employee"."id" = 5;
SELECT ... WHERE "employee"."id" = 4;
SELECT ... WHERE "employee"."id" = 4;

Data Loader

def resolve_tasks(self, args, context, info):

    ids = args.get('ids')
    return Promise(
        DataLoader.load(ids),
    ).get()

SQL with Data Loader

SELECT ... WHERE "employee"."id"
  IN (1, 2, 3, 4, 5);
SELECT ... WHERE "employee"."id"
  IN (1, 2, 3, 6, 7);

Data Loader

def resolve_tasks(self, args, context, info):

    ids = args.get('ids')
    return Promise(
        context.data_loader.load(ids),
    ).get()

SQL with Caching

SELECT ... WHERE "employee"."id"
  IN (1, 2, 3, 4, 5);
SELECT ... WHERE "employee"."id"
  IN (         6, 7);

Relay


Frontend :(

class Task extends React.Component {
  render() {
    return <li key={title}>
             <b>{title}</b> {description}
           </li>
  }
}
Task = Relay.createContainer(Task, {
  task: () => Relay.QL`
    fragment on Task {
      title,
      description,
    }
  `,
});

Pros

  • Conditional fetching
  • Cache
  • Pagination
  • Optimistic updates

Summarize

Pros

  • No extra roundtrips
  • No overfetching
  • Frontend goodies

Cons

  • Backend nightmare

Questions?