はじめに
URQLでGraphQLのSubscriptionを使用しようとした際に結果が表示されないというエラーが発生し、長い間悩まされたので同じ被害者が出ないように残しておこうと思います。
TL;DR
- サーバー(NestJS), クライアント(Next.js)を
graphql-ws
で統一しても動作しない -
subscriptions-transport-ws
では正常に動作する
環境
ツール・ライブラリ | 用途 | バージョン |
---|---|---|
Next.js | フロントエンドとして利用 | 12.1.6 |
NestJS | サーバーサイドとして利用 | 8.4.6 |
GraphQL Code Generator | コード自動生成ツール | 2.6.2 |
URQL | GraphQL Clientのライブラリ | 2.2.1 |
node.js | フロント、サーバーサイドの実行環境 | v16.14.2 |
課題
課題内容は冒頭でも簡単に書いたのでこのエラーに嵌った経緯を書いていきます。
まずGraphQLのSubscriptionを使用する際はリクエストを送信するためのプロトコルは定義されていないので、WebSocket上でサブスクリプションを実装した以下のライブラリのどちらかを使う必要があります。
- graphql-ws
- subscriptions-transport-ws
subscriptions-transport-ws
は活発にメンテナンスが行われておらず、公式ではその後継にあたるgraphql-ws
を使用することを推奨していました。
なので公式に倣ってgraphql-ws
を利用しました。
しかし、結果は以下のようにdataも表示されなければerrorも表示されませんでした。
result:
data: undefined
error: undefined
extensions: undefined
fetching: false
operation: undefined
stale: false
ちなみにサーバー側はNestJSで実装し、graphql-ws
, subscriptions-transport-ws
の両方をonにしました。
※公式でも両方利用することを許容していました。
解決方法
URQLクライアントのSubscription WSをgraphql-ws
からsubscriptions-transport-ws
に変更したところ動作しました。
import {
createClient,
dedupExchange,
Exchange,
fetchExchange,
Provider,
} from 'urql'
import { authConfig } from '@/utils/urql/urql-auth-config'
import { graphCacheConfig } from '@/utils/urql/urql-graphcache-config'
import { subscriptionCustomExchange } from '@/utils/urql/urql-subscription-config'
export const urqlExchanges = [
dedupExchange,
cacheExchange(graphCacheConfig),
authExchange(authConfig),
fetchExchange,
subscriptionCustomExchange,
].filter(Boolean) as Exchange[]
const createUrqlClient = () =>
createClient({
url: 'http://localhost:8000/graphql',
suspense: true,
exchanges: urqlExchanges,
})
import { SubscriptionClient } from 'subscriptions-transport-ws'
import { Exchange, subscriptionExchange } from 'urql'
const isSSR = typeof window === 'undefined'
console.log({ isSSR })
const transportWsClient = !isSSR
? new SubscriptionClient('ws://localhost:8000/graphql', { reconnect: true })
: undefined
export const subscriptionCustomExchange: Exchange | null = transportWsClient
? subscriptionExchange({
forwardSubscription: operation => {
return transportWsClient.request(operation)
},
})
: null
余談
こちらにも書いてあるように、graphql-ws
ライブラリのサブプロトコルをgraphql-transport-ws
と呼び、subscriptions-transport-ws
ライブラリのサブプロトコルをgraphql-ws
と呼んでいます。
なんともまぁややこしいです笑
今回のケースで言うと、プロトコルをgraphql-transport-ws
からgraphql-ws
に変えたらうまく動作することができました。
公式にも書いてあるとおり活発なメンテナンスが行われていないため非推奨になっているのでgraphql-ws
で使えるようになるのはいつなのかは引き続き調査しようと思います。
ではまた!
参考記事