URQLでSubscriptionを使おうとしたらWebSocketのプロトコルが違った話

## はじめに

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も表示されませんでした。

```jsx
result:
  data: undefined
  error: undefined
  extensions: undefined
  fetching: false
  operation: undefined
  stale: false

```

ちなみにサーバー側はNestJSで実装し、`graphql-ws`, `subscriptions-transport-ws`の両方をonにしました。
※公式でも両方利用することを許容していました。

https://docs.nestjs.com/graphql/subscriptions


## 解決方法

URQLクライアントのSubscription WSを`graphql-ws`から`subscriptions-transport-ws`に変更したところ動作しました。


```ts:urql-client.ts
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,
  })
```


```ts:urql-subscription-config.ts
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
```


## 余談

https://www.apollographql.com/docs/react/data/subscriptions/#the-older-subscriptions-transport-ws-library

こちらにも書いてあるように、`graphql-ws`ライブラリのサブプロトコルを`graphql-transport-ws`と呼び、`subscriptions-transport-ws`ライブラリのサブプロトコルを`graphql-ws`と呼んでいます。

なんともまぁややこしいです笑

今回のケースで言うと、プロトコルを`graphql-transport-ws`から`graphql-ws`に変えたらうまく動作することができました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316760/8330cef1-922e-1fa6-6f84-3fe3b06fb235.png" width=350 />



公式にも書いてあるとおり活発なメンテナンスが行われていないため非推奨になっているので`graphql-ws`で使えるようになるのはいつなのかは引き続き調査しようと思います。

ではまた！

## 参考記事

https://formidable.com/open-source/urql/docs/advanced/subscriptions/#setting-up-subscriptions-transport-ws

https://zenn.dev/musuke/scraps/8b61abd50436bf

https://www.apollographql.com/docs/react/data/subscriptions/#choosing-a-subscription-library