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
- JavaScript client for GraphQL (apollo-client)
- Analyses queries and results to keep data cached
- API to run queries and mutations (react-apollo)
- GraphQL loader (graphql-tag)
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
- Server side package to connect with the pub/sub system (graphql-subscriptions)
- WebSocket client + server (subscriptions-transport-ws)
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