Building HandsApp

A real-time Q&A app using GraphQL

 

slides.com/gerardsans | @gerardsans

Google Developer Expert

Master of Ceremonies

Blogger

International Speaker

Spoken at 48 events in 17 countries

Trainer

Community Leader

900

1K

GraphQL

launchpad.graphql.com

graphql.com

summit.graphql.com

HandsUp App

Solution Architecture

HandsUp Schema

npm install --global graphcool
graphcool init
graphcool push
graphcool console

graphcool-cli

Apollo Client

GraphQL Server

source: blog

Dependencies

New Build Setup

// webpack.dist.config.js
module.exports = {
  module: {
    loaders: [
      {
        test: /\.(graphql|gql)$/,
        exclude: /node_modules/,
        loader: 'graphql-tag/loader',
      },
    ]
  }
};

GraphQL Folder

// src/graphql/Questions.query.gql
#import "./Question.fragment.gql"

query questions {
  allQuestions { 
    ...question
  }
}

// src/graphql/CreateQuestion.mutation.gql
#import "./Question.fragment.gql"

mutation addQuestion($body: String!, $user: ID!) {
  createQuestion(body: $body, userId: $user) { 
    ...question
  }
}

Shared Fragments

// src/graphql/Question.fragment.gql
fragment question on Question {
  id 
  body 
  createdAt 
  _votesMeta { count } 
  user { id username pictureUrl flagged } 
  flagged  
}

Network Interface Setup

// client.js
import ApolloClient, { createNetworkInterface } from 'apollo-client'

const networkInterface = createNetworkInterface({
  uri: 'https://api.graph.cool',
  dataIdFromObject: record => record.id, // will be used by caching
})
export const client = new ApolloClient({ networkInterface })

Bootstrap

// app.js
import { ApolloProvider } from 'react-apollo'

let combinedReducer = combineReducers({
  filter, 
  apollo: client.reducer(),
})

const store = compose(
  applyMiddleware(client.middleware())
)(createStore)(combinedReducer)

render(
  <ApolloProvider store={store} client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById('root')
)

Main APIs

  • query
  • mutate
  • updateQueries (reducer)

Schema Involved

schema { 
  query: Query,  
  mutation: Mutation 
}

type Query {
  allQuestions(skip: Int, take: Int): [Question!]!
}

type Mutation {
  createQuestion(body: String!): Question
}

query

// List.js
import { graphql } from 'react-apollo'
import gql from 'graphql-tag'

const withQuestions = graphql(
  gql`query questions {
    allQuestion {
      id body
    }
  }`, {
    props: ({ ownProps, data }) => {
      if (data.loading) return { userLoading: true }
      if (data.error) return { hasErrors: true }
      return {
        list: data.allQuestions,
      }
    }
  })

mutate

// App.js
const withAddQuestion = graphql(
  gql`mutation addQuestion($body: String!) {
    createQuestion(body: $body) { 
      id body
    }
  }`,
  {
    props: ({ ownProps, mutate }) => ({
      addQuestion(text) {
        return mutate({
          variables: { body: text },
          updateQueries: {
            todos: (state, { mutationResult }) => {
              return {
                allQuestions: [...state.allQuestions, 
                  mutationResult.data.createQuestion
                ],
              }
         ...

Subscriptions

GraphQL Server

source: blog

Dependencies

Subscriptions Setup

// client.ts
import { SubscriptionClient, addGraphQLSubscriptions } from 'subscriptions-transport-ws'

const wsClient = new SubscriptionClient('wss://subscriptions.graph.cool/v1', {
  reconnect: true,
})

const networkInterface = ...

const networkInterfaceWithSubscriptions = addGraphQLSubscriptions(
  networkInterface,
  wsClient
)

export const client = new ApolloClient({
  networkInterface: networkInterfaceWithSubscriptions,
})

Subscription Schema

schema { 
  query: Query,  
  mutation: Mutation,
  subscription: Subscription 
}

type Subscription {
  Question(filter: {
    mutation_in: [CREATED]
  }) {
    node {
      id 
      body 
    }
  }
}

Subscribe

// QuestionList.js
const withSubscription = graphql(QUESTIONS_QUERY,
  {
    props: ({ data: { subscribeToMore } }) => ({
      subscribeToNewQuestions() {
        return subscribeToMore({
          document: QUESTIONS_SUBSCRIPTION,
          updateQuery: (state, { subscriptionData }) => {
            const newQuestion = subscriptionData.data.Question.node
            if (!isDuplicate(newQuestion.id, state.allQuestions)) {
              return update(state, {
                allQuestions: {
                  $push: [newQuestion],
                },
              })
            }
          },
        })
      },
    }),
  },
)

More

gsans/handsup-react

Building a real-time Q&A app using GraphQL

By Gerard Sans

Building a real-time Q&A app using GraphQL

Everyone is excited about Subscriptions, the new real-time GraphQL feature. In this talk we are going to cover the full Subscriptions stack and share our learnings while building HandsUp the OSS real-time voting app used at GraphQL Europe!

  • 3,277