In Apollo Client 4, how do you send subscriptions over a GraphQLWsLink WebSocket while queries and mutations stay on HttpLink?
answer
- two terminating links, one chooser
- the link wraps a graphql-ws client
- split on the operation type
- OperationTypeNode.SUBSCRIPTION goes left
- WebSocketLink is the deprecated one
basics
~10 sWrap a graphql-ws client from createClient in GraphQLWsLink, then combine it with an HttpLink using ApolloLink.split, testing operation.operationType against OperationTypeNode.SUBSCRIPTION. Pass the split link as the ApolloClient's required link option.
solid answer
~40 sEach transport needs its own terminating link. `GraphQLWsLink`, from `@apollo/client/link/subscriptions`, wraps a client made by `createClient({ url: "wss://..." })` from the `graphql-ws` library, and `HttpLink` handles everything else. `ApolloLink.split(test, left, right)` sends an operation to `left` when `test` returns `true`, so the test is `({ operationType }) => operationType === OperationTypeNode.SUBSCRIPTION`, with the WebSocket link left and the HTTP link right. The result goes into `new ApolloClient({ link, cache })`, where `link` is required in version 4. Create the `graphql-ws` client once, at module level: it owns the socket, so one created per render or per component can open extra sockets. `WebSocketLink` from `@apollo/client/link/ws` still ships in 4.x but is deprecated; it speaks the older `subscriptions-transport-ws` protocol and fits only a server that still does.
code
ts · 18 linesimport { OperationTypeNode } from "graphql";
import { ApolloClient, ApolloLink, HttpLink, InMemoryCache } from "@apollo/client";
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
const httpLink = new HttpLink({ uri: "https://support.example.com/graphql" });
const wsLink = new GraphQLWsLink(
createClient({ url: "wss://support.example.com/graphql" })
);
const link = ApolloLink.split(
({ operationType }) => operationType === OperationTypeNode.SUBSCRIPTION,
wsLink,
httpLink
);
export const client = new ApolloClient({ link, cache: new InMemoryCache() });go deeper
Recall the two links, GraphQLWsLink for subscriptions and HttpLink for the rest, and that a split chooses between them by operation type.
Explain the split's argument order, why GraphQLWsLink wraps a graphql-ws client rather than a URL, and why the client must be created once for the whole app.
Show what reaches the socket and what does not, where upstream links sit relative to the split, and how to plan a move off the deprecated WebSocketLink with the server.
Weigh running two transports against one: operational cost of long-lived sockets, proxy and scaling support, and whether the product needs push at all.
## Why a chat app needs two transports A support chat room loads its history with a query, sends messages with a mutation, and receives new messages through a subscription. Queries and mutations are one request, one response: plain HTTP suits them, is stateless, and works with the proxies and caches already in the path. A subscription needs a connection the server can keep pushing into. Apollo Client therefore usually runs **two terminating links**, one per transport, and a **split** that chooses between them for each operation. A **link** is one step in Apollo Client's request pipeline; a **terminating link** is the last step, the one that actually talks to the network. ## The pieces | Piece | Import | Job | |---|---|---| | `HttpLink` | `@apollo/client` | sends queries and mutations as HTTP requests | | `createClient` | `graphql-ws` | creates the WebSocket client that owns the socket | | `GraphQLWsLink` | `@apollo/client/link/subscriptions` | adapts that client into a terminating link | | `ApolloLink.split` | `@apollo/client` | routes each operation to one of two links | | `OperationTypeNode` | `graphql` | the enum holding `QUERY`, `MUTATION`, `SUBSCRIPTION` | `GraphQLWsLink` takes **one argument, a `graphql-ws` client**, not a URL or an options object. The URL and the connection settings belong to `createClient`. ## Wiring it step by step 1. Install `graphql-ws` next to `@apollo/client`, `graphql` and `rxjs`. 2. Create the HTTP link: `new HttpLink({ uri: "https://support.example.com/graphql" })`. 3. Create the WebSocket link: `new GraphQLWsLink(createClient({ url: "wss://support.example.com/graphql" }))`. 4. Combine them: `ApolloLink.split(({ operationType }) => operationType === OperationTypeNode.SUBSCRIPTION, wsLink, httpLink)`. When the test returns `true` the operation goes to the second argument, otherwise to the third. 5. Pass the result as `link` to `new ApolloClient({ link, cache: new InMemoryCache() })`. Apollo Client 4 removed the `uri` shortcut, so `link` is required. `operation.operationType` is new in Apollo Client 4 and is never null. Code written for version 3 usually ran `getMainDefinition(query)` and compared `definition.operation` to `"subscription"`; that still works, but the new field is simpler. The bare `split` function is deprecated in favour of the static `ApolloLink.split`. ## Where the split sits in a longer chain Real clients put links in front of the split, combined with `ApolloLink.from([...])`: - Links **before** the split, such as an error link or a link that sets auth headers, run for every operation, on both transports. An `ErrorLink` placed there sees failures from the socket and from HTTP alike, which is usually what you want for logging. - Only what a terminating link actually reads reaches the network. `GraphQLWsLink` forwards the query, variables, operation name and extensions; it does **not** forward context, so HTTP headers set upstream never reach the socket. Socket authentication goes through the `graphql-ws` client's `connectionParams` instead. - Both branches of the split must end in a terminating link, or have one after the split. ## Choosing the right WebSocket link Two client libraries exist for GraphQL over WebSocket, and they are not compatible with each other: - **`graphql-ws`**, used by `GraphQLWsLink`, is the maintained one. - **`subscriptions-transport-ws`**, used by `WebSocketLink` from `@apollo/client/link/ws`, is unmaintained. `WebSocketLink` still ships in 4.3.1, warns in development that it is deprecated, and will be removed in a future major. The client must speak whatever the server speaks, so moving from `WebSocketLink` to `GraphQLWsLink` is a server change first. ## Checking the wiring - In development, `ApolloLink.split` warns when the test returns something other than a boolean, such as a promise from an `async` test function, which is always truthy and would send every operation left. - The browser's network panel should show HTTP requests for the room history and one WebSocket connection carrying the subscription frames; subscription traffic on HTTP means the branches or the test are wrong. - A development warning mentioning `subscriptions-transport-ws` means a `WebSocketLink` is still constructed somewhere. ## Common mistakes - Swapping the split's branches, which sends every query over the socket and every subscription over HTTP. - Calling `createClient` inside a component or a hook, which can open a socket per instance instead of sharing one. - Passing a URL straight to `GraphQLWsLink`. - Expecting an auth header added by an upstream link to authenticate the WebSocket.
- How do you migrate an Apollo Client app from WebSocketLink to GraphQLWsLink?Replace `subscriptions-transport-ws` with `graphql-ws`, import `GraphQLWsLink` from `@apollo/client/link/subscriptions` instead of `WebSocketLink` from `@apollo/client/link/ws`, and pass it `createClient({ url })`. `connectionParams` moves from the nested `options` object to the top level of `createClient`'s options. The two libraries speak different WebSocket protocols, so the server must support the `graphql-ws` one before the client switches.
- In Apollo Client 4, what does ApolloLink.split do when you leave out its third argument?The right-hand branch defaults to a link that forwards the operation to the next link in the chain. So `ApolloLink.split(test, wsLink)` sends matching operations to the WebSocket link and passes the rest on, which works only when a terminating link such as `HttpLink` follows it in `ApolloLink.from([...])`.
- GraphQLWsLink can carry queries and mutations too; why keep them on HTTP?They are one request and one response, so they gain nothing from a stateful connection. On HTTP they keep status codes, per-request headers set by upstream links, and caching and scaling that work without sticky sockets. Apollo's docs recommend HTTP for queries and mutations in most cases, as more efficient and scalable when no WebSocket is otherwise needed.
saying these in an interview costs you the question
- Pass uri to new ApolloClient and add the WebSocket link beside it.
- WebSocketLink with subscriptions-transport-ws is the current Apollo WebSocket link.
- GraphQLWsLink takes the WebSocket URL directly as its option.
- When split's test returns true, the operation goes to the last link passed.
- Creating the graphql-ws client inside a component is fine because Apollo shares sockets.
- An auth header set by an upstream link also authenticates the WebSocket.