Пошаговое руководство по созданию масштабируемого приложения для чата в реальном времени с использованием бессерверных технологий с небольшой помощью NextAuth.js для входа в GitHub. Кому нужны WebSockets, когда у вас есть Live Queries?

Если вы создаете приложения, которые работают с данными в реальном времени, вы, вероятно, используете WebSockets. Они позволяют веб-браузеру и веб-серверу обмениваться данными в режиме реального времени, сохраняя постоянное соединение между ними открытым — данные передаются клиентам, как только они становятся доступными, вместо того, чтобы клиент постоянно опрашивал сервер для проверки новых данных. данные.

Но что, если ваше приложение бессерверное — работает в инфраструктуре, управляемой облачным провайдером?

Чтобы обеспечить высокую эластичность и отказоустойчивость, эти среды спроектированы так, чтобы быть без сохранения состояния и эфемерными по самой своей природе, это означает, что нет никакой гарантии, что ваш код будет запущен на одном и том же физическом сервере после одного запроса. к следующему — и, таким образом, отсутствует постоянное соединение между клиентом и сервером.

Так в чем секрет приложений реального времени без WebSockets? Давайте выясним это, создав этот клон Slack/Discord, используя Next.js в качестве нашей JS-инфраструктуры, Fauna (используя GraphQL) в качестве нашей базы данных и WunderGraph в качестве серверной части для внешнего интерфейса (BFF). ), что облегчает связь между ними. Наше приложение также будет использовать входы GitHub, и мы будем использовать NextAuth.js для наших нужд аутентификации.

Вы найдете репозиторий GitHub для этого руководства здесь.

Живые запросы на сервере — секретный соус

Спецификация GraphQL определяет подписки — вы настраиваете соединение WebSocket с отслеживанием состояния между клиентом и сервером, а затем подписываетесь на событие на сервере. Когда сервер видит событие, соответствующее определенной подписке, он отправляет запрошенные данные через соединение WebSocket клиенту — и мы получаем наши данные.

💡Это объяснение немного сумбурно, но потерпите меня. Рассмотрение различий между транспортами graphql-ws (GraphQL через WebSocket), graphql-helix (GraphQL через SEE) и @n1ru4l/socket-io-graphql-server (GraphQL через Socket.io) немного выходит за рамки учебника.

Если вы не можете использовать веб-сокеты (например, на бессерверных платформах, как упоминалось ранее), вы не можете использовать подписки…но тут на помощь приходят живые запросы!

Они не являются частью спецификации GraphQL, поэтому определения различаются в зависимости от клиентской библиотеки; но по сути, в отличие от подписок, Live Queries:

  1. Стремитесь подписаться на текущее состояние данных сервера, а не на события (поэтому, новые полезные данные всякий раз, когда запрос будет давать другие данные).
  2. Вместо этого можно использовать обычный старый HTTP-опрос с интервалом, когда соединения WebSocket недоступны.

Я вижу, как читатели берут вилы прямо по сигналу.

«Опрос HTTP на стороне клиента для получения данных в реальном времени?! Это безумно дорого! Что произойдет с несколькими пользователями в одном чате?»

Все обоснованные опасения! Но… тут на помощь приходит WunderGraph. Видите ли, мы не собираемся опрашивать эти данные на стороне клиента. Вместо этого мы перекладываем эти функции Live Query на WunderGraph, наш сервер Backend-for-Frontend.

💡 WunderGraph — это инструмент разработки, который позволяет вам определять зависимости данных — GraphQL, API REST, базы данных Postgres и MySQL, федерации Apollo и все, что вы можете придумать — как конфигурацию как код. , а затем анализирует их и превращает все это в один виртуальный граф, который вы затем можете запрашивать и изменять с помощью GraphQL, а затем предоставлять вашему клиенту как JSON-over-RPC.

Когда вы выполняете интерактивные запросы из слоя WunderGraph, независимо от того, сколько пользователей подписано на ваш интерактивный чат во внешнем интерфейсе, в любой момент времени у вас будет активен только один экземпляр опроса, для всего вашего приложения.

Гораздо лучше, чем необработанный опрос на стороне клиента; но это идеально? Нет — интервал опроса по-прежнему добавляет некоторую задержку и не идеален для приложений, которым требуется по-настоящему мгновенная обратная связь, а без исправления JSON полныйрезультат запроса будет отправлен клиенту по сети. каждый раз, даже с неизмененными данными — но для нашего варианта использования это более чем приемлемо.

Архитектура

Давайте поговорим о потоке этого приложения.

Наши пользователи входят в чат со своей учетной записью GitHub (через OAuth). Мы создаем чат рабочей группы/внутренней команды, поэтому использование GitHub в качестве провайдера имеет смысл.

Использование NextAuth значительно упрощает нашу историю авторизации — и даже имеет официальную интеграцию с Fauna — безсерверной базой данных! Это делает последний действительно хорошим выбором в качестве базы данных как для аутентификации, так и для бизнес-логики (хранение сообщений чата).

Наш интерфейс Next.js относительно прост благодаря WunderGraph — он предоставит нам автоматически сгенерированные (и типобезопасные!) запросы и перехватчики мутаций, построенные поверх SWR Vercel, и это то, что наши компоненты будут использовать для получения (сообщений чата, и список онлайн-пользователей) и писать (новые сообщения чата) данные.

Давайте начнем!

Шаг 0: Настройка

Next.js + WunderGraph

Интерфейс командной строки WunderGraph create-wunder-graph — это лучший способ настроить как наш сервер BFF, так и интерфейс Next.js за один раз, так что давайте сделаем именно это. Просто сначала убедитесь, что у вас установлена ​​последняя версия Node.js LTS.

npx create-wundergraph-app my-project -E nextjs

Затем перейдите в каталог проекта и:

npm install && npm start

NextAuth

Наша настройка NextAuth включает в себя небольшую работу. Давайте сначала уберем базовый пакет.

npm install next-auth

Затем получите адаптер NextAuth для FaunaDB. «Адаптер» в NextAuth.js подключает ваше приложение к любой базе данных или серверной системе, которую вы хотите использовать для хранения данных о пользователях, их учетных записях, сеансах и т. д. Адаптеры технически необязательны, но поскольку мы хотим сохранить пользовательские сеансы для нашего приложения , нам понадобится один.

npm install @next-auth/fauna-adapter faunadb

Наконец, нам понадобится «провайдер» — доверенные службы, которые можно использовать для входа пользователя. Вы можете определить своего собственного провайдера OAuth, если хотите, но здесь мы можем просто использовать встроенного провайдера GitHub.

Прочитайте документы GitHub OAuth, чтобы узнать, как зарегистрировать свое приложение, настроить его и получить URL-адрес и секретный ключ из вашей учетной записи GitHub (НЕ НЕОБЯЗАТЕЛЬНО). Для URL-адреса обратного вызова в настройках GitHub используйте http://localhost:3000/api/auth/callback/github

Наконец, когда у вас есть идентификатор клиента GitHub и секретный ключ, поместите их в свой ENV-файл Next.js (как GITHUB_ID и GITHUB_SECRET) соответственно.

Шаг 1: Данные

Fauna — это географически распределенная (идеально подходящая для Vercel и Netlify эпохи Serverless/Edge) документно-реляционная база данных, цель которой — предложить лучшее из мира SQL (моделирование на основе схемы) и NoSQL (гибкость, скорость).

Он предлагает GraphQL из коробки, но самая интересная особенность Fauna заключается в том, что он упрощает определение хранимых процедур и предоставляет их как запросы GraphQL через схему. (Они называют это Определяемые пользователем функции или UDF). Очень крутая штука! Мы будем широко использовать это.

Зарегистрируйтесь в Fauna, создайте базу данных (без демонстрационных данных), запишите свой URL-адрес и секрет в ENV Next.js (как FAUNADB_GRAPHQL_URL и FAUNADB_TOKEN соответственно), и вы можете перейти к следующему шагу.

Авторизация

Чтобы использовать Fauna в качестве нашей базы данных аутентификации, нам нужно определенным образом определить ее коллекции (например, таблицы) и индексы (все поиски в Fauna выполняются с их использованием). NextAuth проводит нас через эту процедуру здесь, так что это просто вопрос копирования и вставки этих команд в вашу оболочку Fauna.

CreateCollection({ name: "accounts" })
CreateCollection({ name: "sessions" })
CreateCollection({ name: "users" })
CreateCollection({ name: "verification_tokens" })


CreateIndex({
  name: "account_by_provider_and_provider_account_id",
  source: Collection("accounts"),
  unique: true,
  terms: [
    { field: ["data", "provider"] },
    { field: ["data", "providerAccountId"] },
  ],
})
CreateIndex({
  name: "session_by_session_token",
  source: Collection("sessions"),
  unique: true,
  terms: [{ field: ["data", "sessionToken"] }],
})
CreateIndex({
  name: "user_by_email",
  source: Collection("users"),
  unique: true,
  terms: [{ field: ["data", "email"] }],
})
CreateIndex({
  name: "verification_token_by_identifier_and_token",
  source: Collection("verification_tokens"),
  unique: true,
  terms: [{ field: ["data", "identifier"] }, { field: ["data", "token"] }],
})

Теперь, когда мы настроили Fauna для удовлетворения наших потребностей в аутентификации, вернитесь в каталог вашего проекта и создайте файл ./pages/api/auth/[…nextauth.ts] со следующим содержимым:

Вот и все, мы настроили базовую аутентификацию! Мы проверим это через минуту. Но сначала…

Бизнес-логика

Пришло время определить схему GraphQL, необходимую для нашей бизнес-логики, то есть пользователей, чатов и сеансов (с небольшими изменениями для учета существующей схемы NextAuth). Перейдите на вкладку GraphQL на панели инструментов Fauna и импортируйте эту схему.

Почему chat имеет правильное отношение к пользователям, а sessions нет? Это ограничение того, как NextAuth в настоящее время взаимодействует с Fauna для хранения пользовательских сеансов, но мы обойдем его, как вы скоро увидите.

Видите последний запрос, отмеченный директивой @resolver? Это хранимая процедура Fauna/определяемая пользователем функция, которую мы будем создавать! Перейдите на вкладку «Функции» и добавьте его.

getUserIDByEmail

Query(
  Lambda(
    ["email"],
    Select(["ref", "id"], Get(Match(Index("user_by_email"), Var("email"))))
  )
)

Знакомство с синтаксисом FQL помогает, но эти функции должны говорить сами за себя — они делают именно то, что следует из их названий — принимают аргумент и ищут значения, которые соответствуют ему, используя индексы, определенные ранее. При использовании с директивой @resolver в нашей схеме они теперь отображаются как запросы GraphQL — невероятно полезно. Вы можете делать практически все, что захотите, с пользовательскими функциями Fauna и возвращать любые данные, какие захотите.

Шаг 2: Зависимости данных и операции

WunderGraph работает, анализируя все источники данных, которые вы определяете в массиве зависимостей, и создавая для них модели данных.

Во-первых, убедитесь, что ваш файл ENV настроен правильно:

GITHUB_ID="XXXXXXXXXXXXXXXX"
GITHUB_SECRET="XXXXXXXXXXXXXXXX"
FAUNA_SECRET="XXXXXXXXXXXXXXXX"
FAUNADB_GRAPHQL_URL="https://graphql.us.fauna.com/graphql"
FAUNADB_TOKEN="Bearer XXXXXXXXXXXXXXXX"

Замените своими значениями иперепроверьте, чтобы убедиться, что URL-адрес Fauna соответствует региону, в котором вы размещаете свой экземпляр!

…а затем добавьте нашу базу данных фауны в качестве зависимости в WunderGraph, и пусть она делает свое дело.

Затем вы можете написать запросы/мутации GraphQL для определения операций над этими данными (они находятся в каталоге .wundergraph/operations), и WunderGraph создаст безопасные клиентские обработчики Next.js для доступа к ним.

Получение всех сообщений здесь вместе со связанными с ними пользователями.

Способ работы NextAuth с GitHub заключается в том, что он сохраняет текущие активные сеансы в базе данных с полем expires. Вы можете прочитать userId из активной таблицы sessions , и тот, кому принадлежит этот userId , может считаться находящимся в данный момент в сети.

Итак, этот запрос GraphQL извлекает всех пользователей, у которых в данный момент есть активный сеанс — мы будем использовать это в нашем пользовательском интерфейсе, чтобы указать тех, кто находится в сети. WunderGraph упрощает выполнение JOIN с помощью нескольких запросов — и с помощью поля _join (и директивы @transform для сокращения ненужной вложенности), мы можем преодолеть NextAuth, не добавляя надлежащие отношения в Fauna — показано здесь, пока извлечение пользователя, связанного с сеансом, по его userId.

Благодаря достаточно короткому сроку жизни токенов GitHub OAuth вам не придется беспокоиться об устаревших данных, когда речь идет о онлайн-пользователях, а NextAuth достаточно умен, чтобы в любом случае аннулировать устаревшие токены, когда эти пользователи пытаются войти в систему с просроченным токеном. .

И, наконец, это наша единственная мутация, которая срабатывает, когда пользователь, вошедший в систему, отправляет новое сообщение. Здесь вы видите, как Фауна обрабатывает отношения в Mutations — поле connect (подробнее здесь) используется для связи текущего создаваемого документа (сообщение в чате) с существующим документом (пользователем)

Шаг 3: Переходим к внешнему интерфейсу

Вам придется обернуть свое приложение в <SessionProvider>, чтобы иметь возможность использовать useSession hooks NextAuth, раскрывая контекст сеанса на верхнем уровне вашего приложения.

Кроме того, для стилизации я использую TailwindCSS — инструкции по настройке с помощью Next.js здесь.

Хуки NextAuth упрощают реализацию авторизации — второй части auth. Используйте useSession , чтобы проверить, вошел ли кто-то в систему (это возвращает объект пользователя с именем пользователя GitHub, адресом электронной почты и URL-адресом аватара — отлично!), а функции signIn и signOut автоматически перенаправляют пользователей на эти страницы.

Вы можете сами стилизовать пользовательские страницы NextAuth signIn/signOut pages, если хотите (инструкции здесь), но стили по умолчанию, небрендированные стили прекрасно подходят для наших нужд.

Шаг 4. Отображение онлайн-пользователей

Здесь особо не на что смотреть; наша стратегия определения онлайн-пользователей уже упоминалась в запросе GraphQL для этого.

Шаг 5: Окно чата (канал и ввод)

Здесь мы видим, как WunderGraph может превратить любой стандартный запрос в Live Query всего с одной добавленной опцией — liveQuery: true. Для этого вам даже не нужно использовать собственный API GraphQL, такой как Fauna, интерактивные запросы WunderGraph работают с любым источником данных.

Однако, чтобы точно настроить интервалы опроса для Live Queries, проверьте ./wundergraph/wundergraph.operations.ts и измените это значение в секундах.

Для метки времени мы будем использовать простую служебную функцию, чтобы получить текущее время в эпохе — количество секунд, прошедших с 1 января 1970 года (полночь UTC/GMT) — и преобразовать его в удобочитаемую строку для отображения. хранится в нашей базе данных.

Шаг 6: Панель навигации

Компонент NavBar снова использует useSession hook NextAuth, чтобы получить имя текущего пользователя GitHub и URL-адрес аватара и отобразить их. Он также использует signOut для выхода из системы.

Готово! Отправляйтесь на tolocalhost:3000 и попробуйте. Для скриншотов здесь я использую две учетные записи GitHub в двух разных браузерах, но я протестировал это с пятью пользователями, и все работает отлично.

Вот и все, Народ!

Объединение NextAuth, Fauna и WunderGraph для GraphQL Live Queries — это мощная комбинация, которая хорошо поможет вам в создании интерактивных чатов в реальном времени — независимо от того, создаете ли вы что-то простое для небольшого сообщества или сложные платформы корпоративного уровня. .

Для бессерверных приложений использование WunderGraph в качестве серверной части для внешнего интерфейса с GraphQL Live Queries обеспечит гораздо меньшую задержку, чем опрос на стороне клиента — только один экземпляр опроса для всех клиентов, подписанных на приложение чата, означает снижение нагрузки на сервер и сетевой трафик.

Вот некоторые вещи, которые вы должны отметить в будущем:

Для качества жизни

  • Оставьте liveQuery: true commented отключенным, пока вы создаете пользовательский интерфейс, и включайте его только при тестировании функций чата. Вы же не хотите перегружать сервер Fauna звонками, особенно на ограниченном бесплатном уровне!
  • Если вы внесете изменения в свою схему Fauna GraphQL во время разработки, вы, вероятно, обнаружите, что самоанализ WunderGraph не улавливает новые изменения. Это сделано намеренно:WunderGraph создает кеш после первого самоанализа и вместо этого работает с ним, чтобы каждый раз сокращать ресурсы, затрачиваемые на полный самоанализ.

Чтобы обойти это, запустите npx wunderctl generate –-clear-cache, если вы внесли изменения в схему базы данных во время разработки и хотите перегенерировать модели и хуки.

Для развертываний

Если вам нужно что-то уточнить относительно WunderGraph как шлюза BFF/API, зайдите в их сообщество Discord, здесь. Удачного кодирования!