Автор оригинала: Gerard Sans 🌂🇬🇧.
Использование подписок GraphQL, клиент Apollo 1.0 и AUTH0
В этой статье мы рассмотрим все шаги, которые были вовлечены, чтобы построить приложение App Real-Time с открытым исходным кодом, использующим Graphql и реагировать.
- Архитектура решения : Приложение для руки
- GraphQL Server : Создайте его с помощью Graphcool CLI
- Apollo Client : Настройка Bootstrap и интеграция на реагирование
- Запросы : Отображение вопросов
- Мутации : Голосование и отслеживание голосов с использованием опроса
- Аутентификация пользователей : Настройка AUTH0, AUTH0, интеграция на реагирование, интеграция пользователя Graphcool и отображение зарегистрированного пользователя
- Подписки : Добавление новых вопросов и подписки к ним
Это приложение позволит участникам события задавать вопросы (если войти в систему) и проголосовать за самые интересные.
Вы можете увидеть окончательный результат ниже:
Все пользователи смогут проголосовать вопросы, повышая их важность. Для того, чтобы добавить новые вопросы, пользователи должны войти в систему. Поскольку новые вопросы и голоса записываются, пользователи обновляются. Вся эта информация затем может использоваться организаторами мероприятий для запуска Q & Sessions или панелей во время события.
Вы можете получить доступ к окончательному решению в Github Отказ
Найдите последний контент GraphQL, следующий за моим каналом @gerardsans Отказ
Архитектура решения: приложение для рук
Для реализации этих функций мы будем использовать Graphql Отказ Это также для того, чтобы показать, как мы можем использовать свои функции в реальном времени с помощью подписок.
Для введения в Graphql и Apollo Client Вы можете прочитать этот блогпост.
Graphql и удивительный клиент Аполлона
На стороне сервера мы собираемся использовать Графа Как наше GraphQL Server Отказ
На стороне клиента мы собираемся использовать Apollo Client Как наш мост между Реагировать и GraphQL Server Отказ Мы будем использовать AUTH0 Таким образом, пользователи могут войти в наше приложение, используя свои социальные счета.
Наконец, мы добавим Подписки GraphQL Для достижения обновлений в реальном времени для новых вопросов.
Смотрите обзор нашей архитектуры ниже:
Строительные блоки для нашего приложения голосования в реальном времени
Создание сервера GraphQL
Давайте начнем создавать наши GraphQL Server Отказ Это может звучать вполне сложно, но, используя Graphcool cli , очень легко. Идти вперед и Зарегистрируйтесь прежде чем делать следующие шаги.
Мы будем использовать Graphcool cli создать наши Модель данных (Схема). Запустите эти команды, чтобы установить CLI и создать новый проект.
npm install --global graphcool graphcool init
Следуйте инструкциям на экране и не забудьте Зарегистрируйтесь первый. Это создаст GraphQL Server провести наши данные. Инатальная схема автоматически генерируется. Обновите Project.Graphcool
Файл, созданный во время init Команда, с последней доступной схемой здесь и запустить команды ниже. Первый будет обновлять схему. Вы можете использовать второй, чтобы открыть Console Graphcool Отказ
graphcool push graphcool console
ПРИМЕЧАНИЕ. Последняя схема может включать в себя больше полей и типов, так как в проекте добавляются больше функций.
Это последний Модель данных Мы будем использовать для этой статьи.
График Вид в консоли графа
Как только вы более знакомы с GraphQl, вы можете создать свой собственный GraphQL Server Отказ Это совершенно нормально. Читать Этот блогпост по Джонас Хельфер Узнать больше.
Apollo Client.
Apollo Client Это клиент Framework-Agnostic GraphQL, который помогает вам получать данные и сохранить состояние клиента в синхронизации с сервером.
Настройка Bootstrap
Для того, чтобы настроить Apollo Client Нам нужно добавить некоторые зависимости в наш проект.
npm install --save apollo-client react-apollo graphql-tag
Это установит необходимые зависимости для запуска наших запросов GraphQL. Мы создадим разделенный файл Client.js
держать наш Apollo Client настраивать.
// src/client.js import ApolloClient, { createNetworkInterface } from 'apollo-client' const networkInterface = createNetworkInterface({ uri: 'https://api.graph.cool/simple/v1/YOUR_KEY_HERE', dataIdFromObject: record => record.id, }) export const client = new ApolloClient({ networkInterface, })
Для того, чтобы получить ваш ключ Вы можете запустить Конечные точки графов
Использование Graphcool cli Отказ
От React-Apollo
Мы будем использовать Аполлопровидер компонент высокого порядка во время загрузки. Нам нужно пройти клиент Мы просто настроили с помощью атрибута с тем же именем.
// src/app.js import { ApolloProvider } from 'react-apollo' import { client } from './client' render(, document.getElementById('root') )
Нам нужен только маршрут, указывающий на наш основной компонент.
Реагистрационная интеграция
Всякий раз, когда нам нужно использовать Graphql мы расширим наши компоненты, используя этот рисунок ниже
import {graphql} from 'react-apollo' import {QUERY_OPERATION} from './graphql/query.operation.gql' class Component extends React.Component {} const withOperation = graphql(QUERY_OPERATION, {options}) export default withOperation(Component)
При этом мы можем легко расширить наши компоненты, добавляющие конкретные GraphQL Properties (реквизит), которые мы можем использовать для взаимодействия с нашими GraphQL Server Отказ
Есть некоторые настройки, необходимые для вашего WebPack Config для импорта .graphql
или .gql
Расширения. Проверьте graphql-tag/loader Отказ Основная выгода избегает обработки Graphql ast (абстрактное синтаксическое дерево) на клиенте по сравнению с использованием GQL Запрос
Отказ
Отображение вопросов
Мы начнем с поиска модели данных, необходимой для обработки вопросов. Это будет новый тип имени Вопрос:| Отказ Вы можете увидеть упрощенное определение ниже. Каждый вопрос будет иметь идентификатор, тело и когда он был создан.
type Question { id: ID! body: String! createdAt: DateTime! }
Для того, чтобы справиться с списком вопросов, мы создали Вопрос списки и Вопрос:| составные части. Смотрите упрощенную версию псевдо- HTML ниже:
// src/components/QuestionList.js
... - {question.body}
На фрагменте ниже мы определяем запрос, чтобы получить все доступные вопросы. Фрагмент вопросов позволит нам ссылаться на те же вопросы вопросов в других запросах и/или мутациях.
query questions { allQuestions { ...question } } fragment question on Question { id body createdAt }
Голосование
Получение вопросов от аудитории во время события является ключом, но для больших событий могут быть некоторые ограничения времени, поэтому может быть полезно получить отзыв о том, какие вопросы являются наиболее популярными. Мы можем добиться этого, позволяя посещать проголосовать.
Нам нужно расширить нашу модель с другим типом Голосовать Отказ Каждый голос будет связан с вопросом.
type Vote { id: ID! createdAt: DateTime! question: Question }
Посмотрим, как выглядит мутация голосования:
// src/graphql/Vote.mutation.gql mutation createVote($question: ID!) { createVote(questionId: $question) { id } }
Эта мутация создаст новую запись, используя идентификатор вопроса. Давайте посмотрим, как мы можем построить эту мутацию в наше приложение. Мы собираемся расширить Вопрос:| Компонент, который оказывает вопрос, с новой мутацией
// src/components/Question.js const withVote = graphql(CREATE_VOTE_MUTATION, { props: ({ ownProps, mutate }) => ({ vote(id) { return mutate({ variables: { question: id }, }) }, }), }, ) export default withVote(Question)
Это расширит объект реквизита, чтобы включить голосовать (ID)
Функция, делающая вызов для мутата и передачи идентификатора вопроса, передавая идентификатор вопроса в рамках свойства переменных.
В пределах Вопрос:| Компонент Мы проверим эту мутацию, когда пользователь нажимает на кнопку голосования на этот вопрос. Обратите внимание, как мы использовали оптимистичный интерфейс применяя изменения немедленно, изменяя состояние.
// src/components/Question.js class Question extends React.Component { onSubmit() { this.setState({ votes: this.state.votes+1 }) this.props.vote(this.props.question.id) } // }
Отслеживание голосов Информация
До сих пор мы охватываем, как мы добавили новые голоса на вопрос, но мы не охватываем, как мы сохраним все эти данные актуальными для всех вопросов.
Для того, чтобы сделать это, мы собираемся использовать хорошую функцию из Graphcool под названием Агрегации Отказ Эта функция позволяет нам сохранить текущий счетчик голосов за каждый вопрос, не делая никакого дополнительного кодирования! Для каждого типа Graphcool создает дополнительный тип, который мы можем использовать для доступа к этим данным, в нашем случае это _Votesmeta
Отказ
query questions { allQuestions { ...question } } fragment question on Question { id body createdAt _votesMeta { count } }
Если мы посмотрим на фрагмент вопросов, мы видим некоторые новые поля _Votesmeta
и Считать
Отказ Это позволит нам собрать информацию относительно того, сколько голосов имеет определенный вопрос.
Вопросы голосов опроса
Мы освещаем, как мы отслеживаем новые голоса и о том, как мы извлекаем текущий счетчик голосов. Но мы не охватывали, как мы получаем новые обновления в нашем UI.
Чтобы отобразить информацию о голосовании, мы собираемся настроить опрос, чтобы получить эту информацию о фиксированном графике. Это компромисс, чтобы избежать приема. Синхронизация голосов в режиме реального времени генерирует ненужные суммы трафика для крупных мероприятий.
// src/components/QuestionList.js const withQuestions = graphql(QUESTIONS_QUERY, { options: { pollInterval: POLLING_TIME }, props: ({ data }) => { return { questions: data.allQuestions, } }, }, ) export default withQuestions(QuestionList)
Используя опрос, мы гарантируем, что все пользователи обновляются каждые несколько секунд с последней агрегированной информацией. Поскольку эта информация может быть не совсем точной, мы заменили общий счет голосов для токологического дисплея. Чем больше баров, тем больше возникает вопрос, который имеет вопрос после экспоненциальной функции.
Аутентификация пользователей с использованием AUTH0
Настройка Auth0
Зарегистрируйтесь на Сайт AUTH0 и создать новый Спа-клиент Отказ Мы будем использовать этот спа-клиент для доступа к услугам AUTH0 из нашего приложения React.
Вы можете следовать за этим Блог Для получения более подробной информации о том, как настроить учетную запись AUTH0 и как получить Client_id
и Домен
Отказ
Чтобы обрабатывать пользователя вход в систему и управление сессией, которые мы создали Авторизация услуга.
// src/services/Authorisation.js export default class Authorisation { constructor() { this.lock = new Auth0Lock(CLIENT_ID, DOMAIN, { auth: { responseType: 'id_token', params: { scope: 'openid email' }, redirect: false, }, }) this.lock.on('authenticated', this.doAuthentication.bind(this)) } authenticate() { this.lock.show() } }
Из кода выше, мы используем Auth0lock
Чтобы обеспечить хороший пользовательский интерфейс, чтобы пройти все шаги для пользователя. Это настройка для использования всплывающего наложения без перенаправлений. После настройки Auth0lock
экземпляр, мы зарегистрировали обратный вызов для аутентифицирован
мероприятие. Чтобы отобразить этот всплывающийся замок, мы создали отдельный аутентифицировать
. метод.
Давайте посмотрим, что происходит, как только пользователь входит в систему, и аутентифицирован
событие срабатывает.
// src/services/Authorisation.js doAuthentication(authResult) { if (!this.profile) { this.auth0IdToken = authResult.idToken this.lock.getProfile(authResult.idToken, (error, profile) => { if (error) { this.auth0IdToken = null this.profile = null } else { this.profile = profile } }) } }
Первое, что мы делаем, это хранить Auth0idtoken
В LocalStorage завернутый в добыче и сеттер для удобства.
// src/services/Authorisation.js get auth0IdToken() { return localStorage.getItem('auth0IdToken') } set auth0IdToken(value) { if (value) { localStorage.setItem('auth0IdToken', value) } else { localStorage.removeItem('auth0IdToken') } }
После этого мы постараемся получить доступ к профилю пользователя, используя этот токен. Это асинхронная операция, поэтому мы проходим обратный вызов и используем тот же подход для хранения его в локальной табличке. В случае любых ошибок мы просто очищаем информацию о пользователе.
Интеграция с реакцией
Для того, чтобы интегрировать его с нашим приложением, мы собираемся использовать Компонент высокого порядка это пройдет Авторизация
Ссылка экземпляра класса вниз по дерево компонента.
// src/app.js const auth = new Authorisation() class HandsUpAppWrapper extends React.Component { render() { return () } }
Мы можем использовать Auth0idtoken
Для целей авторизации при общении с нашей GraphQL Server Отказ Для того, чтобы интегрировать два, мы можем использовать промежуточное программное обеспечение для добавления необходимых заголовков.
// src/client.js networkInterface.use([{ applyMiddleware(req, next) { if (localStorage.getItem('auth0IdToken')) { if (!req.options.headers) { req.options.headers = {} } req.options.headers.authorization = `Bearer ${localStorage.getItem('auth0IdToken')}` } next() }, }])
После того, как у нас будет эта настройка, мы можем настроить конкретные запросы, чтобы позволить авторизованным пользователям их носить.
Интеграция пользователя Graphcool
Для того, чтобы использовать информацию о пользователе в нашей модели, нам нужно зарегистрировать новые пользователи, используя Createuser
Мутация предоставлена графом. Наиболее важное поле IDTTOKED, предоставляемое AUTH0. Остальные поля позволят нам отобразить изображение профиля пользователя и имени пользователя.
// src/graphql/CreateUser.mutation.gql mutation createUser( $idToken: String!, $name: String!, $username: String!, $pictureUrl: String! ){ createUser( authProvider: { auth0: { idToken: $idToken } }, name: $name, username: $username, pictureUrl: $pictureUrl ) { id } }
Обратите внимание на ID
поле внизу. Увидеть ниже упрощенной версии Пользователь тип.
type User { id: ID! # graphcool internal id auth0UserId: String # auth0 idToken name: String username: String pictureUrl: String createdAt: DateTime! questions: [Question!]! }
Мы собираемся использовать это, чтобы изменить мутацию вопросов, поэтому мы включаем в систему, вошел в систему.
// src/graphql/CreateQuestion.mutation.gql #import "./Question.fragment.gql" mutation addQuestion($body: String!, $user: ID!) { createQuestion(body: $body, userId: $user) { ...question } }
Вопросов. Отображение пользовательских данных
Поскольку мы используем тот же фрагмент вопросов как в запросе, так и в мутации, мы можем просто продлить его для получения новых полей.
// src/graphql/Question.fragment.gql fragment question on Question { user { id username pictureUrl } }
Отображение зарегистрированного пользователя
Как только у нас есть пользовательская информация от AUTH0, мы можем использовать Авторизация Служба для предоставления этой информации нашим компонентам.
Для отображения зарегистрированного пользователя мы собираемся создать Профиль компонент и поместить его в нашу Топнавигация составная часть. Это будет чистый компонент только в зависимости от Профиль
и islogged
реквизит.
// src/components/TopNavigation.js
Компонент приводит к простому осуществлению.
// src/components/Profile.js class Profile extends React.Component { render() { if (!this.props.isLogged) { return null } return () } }
Добавление новых вопросов
До сих пор мы охватываем все функции для анонимных пользователей. Любой, кто с доступом к нашей заявке может следовать список вопросов и голоса. Давайте добавим вариант для участников, чтобы добавить новые вопросы.
См. Ниже подпрос мутации, чтобы создать новый вопрос.
// src/graphql/CreateQuestion.mutation.gql #import "./Question.fragment.gql" mutation addQuestion($body: String!, $user: ID!) { createQuestion(body: $body, userId: $user) { ...question } }
Мы можем использовать зарегистрированный пользователь, чтобы создать вопрос и связать его к зарегистрированному пользователю в системе.
Давайте посмотрим, как мы интегрировали этот запрос мутации к AddQuestion составная часть
// src/components/AddQuestion.js class AddQuestion extends React.Component { onSubmit(event) { event.preventDefault() this.props .addQuestion(this.input.value, this.props.auth.userId) } render() { return () } } const withAddQuestion = graphql(CREATE_QUESTION_MUTATION, {...}) export default withAddQuestion(AddQuestion)
На код выше, мы можем увидеть, как мы связали OnsUbmit
Форма события к AddQuestion
Передача входное значение и текущий зарегистрированный пользователь.
Обратите внимание, как мы получим доступ к базовому входному элементу, используя ref атрибут Перезвоните.
// src/components/AddQuestion.js const withAddQuestion = graphql(CREATE_QUESTION_MUTATION, { props: ({ mutate }) => ({ addQuestion(body, id) { return mutate({ variables: { body: body, user: id }, updateQueries: { questions: (state, { mutationResult }) => { let newQuestion = mutationResult.data.createQuestion return update(state, { allQuestions: { $push: [newQuestion], }, }) }, }, }) }, }), }, )
В этом случае мы добавляем AddQuestion
Функция нашего компонента как новое свойство (опоры). Мы позвоним Мутате
Прохождение вопроса тело и идентификатор пользователя как Переменные
Отказ Когда мутация возвращается с результатами UsseQueries
будет выполняться. Затем мы можем получить доступ к результату, используя МутацияResult
и возвращая новое состояние.
Чтобы не добавлять побочные эффекты, мы используем Несмотря на молчание
библиотека. Вы можете узнать о его синтаксисе здесь Отказ
Настройка подписок
Чтобы получить доступ к функциям в реальном времени, нам нужно добавить поддержку подписок нашего клиента Apollo. Обязательно запустите эту команду:
npm install — save subscriptions-transport-ws
Настройка подписок клиентов Apollo (Client.js)
Нам также требуется отдельная конечная точка из Graphcool для доступа к подпискам на сервере.
Для того, чтобы получить ваш Ключ подписки , вы можете запустить Конечные точки графов
Использование Graphcool cli Отказ
// src/client.js import { SubscriptionClient, addGraphQLSubscriptions } from 'subscriptions-transport-ws' const wsClient = new SubscriptionClient('wss://subscriptions.graph.cool/v1/YOUR_KEY_HERE', { reconnect: true, }) const networkInterfaceWithSubscriptions = addGraphQLSubscriptions( networkInterface, wsClient ) export const client = new ApolloClient({ networkInterface: networkInterfaceWithSubscriptions, })
На код выше, мы настроили Подписка для покупателя
используя вашу конечную точку. Мы активировали Переподключите
Флаг, поэтому клиент изящно восстановится от транспортных сбоев. Наконец, мы расширяем наш текущий сетевой интерфейс, чтобы включить клиент подписки.
Подписаться на новые вопросы
Одной из ключевых функций для нашего приложения является то, что новые вопросы передаются между всеми участниками в режиме реального времени, поэтому каждый может узнать о них и голосовать.
Давайте посмотрим на нашу подписку:
// src/graphql/Questions.subscription.gql #import "./Question.fragment.gql" subscription { Question(filter: { mutation_in: [CREATED] }) { node { ...question } } }
Синтаксис для подписок требует Тип и а Список операций Нас интересует: созданные, обновленные или удаленные. В коде выше, мы подписываем на новый Вопросы Отказ Поэтому для каждого нового вопроса мы получим сообщение (узел) со всеми полями, определенными в нашем фрагменте вопроса.
Давайте посмотрим, как мы интегрировали эту подписку с Подписки Компонент, который отображает список вопросов.
// src/components/QuestionList.js class QuestionList extends React.Component { componentWillMount() { this.props.subscribeToNewQuestions() } render() {...} } const withQuestions = graphql(QUESTIONS_QUERY, {...}) const withSubscription = graphql(QUESTIONS_QUERY, {...}) export default withSubscription(withQuestions(QuestionList))
Мы продлеваем Вопрос списки Компонент, использующий один и тот же шаблон, что и раньше с новой подпиской. Обратите внимание, как мы использовали ComponentWillmount
Чтобы вызвать подписку. Смотрите код для подписки ниже.
// src/components/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], }, }) } }, }) }, }), }, )
Мы используем подписчик
определить нашу UpdateQuary
Отказ Это будет выполнять каждый раз, когда мы получим сообщение из нашей подписки на запрос Вопросы_subscription
Отказ
Мы можем получить новые данные вопросов, используя ПодпискиДата
имущество. Выход для UpdateQuary
должно быть полученным в результате нового государства, включая новый вопрос.
Чтобы не добавлять побочные эффекты, мы используем Несмотря на молчание
библиотека. Вы можете узнать о его синтаксисе здесь Отказ
Поскольку мы использовали оптимистичные пользователи, нам нужно проверить дубликаты, чтобы избежать двойных записей для пользователя, добавляя вопрос.
Вы можете получить доступ к окончательному решению в Github Отказ
Это все люди! Есть какие-либо вопросы? Спасибо за прочтение! Пинг мне в @gerardsans.
Graphql Лондонское сообщество