Nest JS Microservice TCP E2E Test - microservices

does someone know how to write E2E test for nest microservices? Giving this code?
main.ts
import { NestFactory } from '#nestjs/core';
import { Transport } from '#nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice(AppModule, {
transport: Transport.TCP,
});
app.listen(() => console.log('Microservice is listening'));
}
bootstrap();
app.controller.ts
import { Controller } from '#nestjs/common';
import { MessagePattern } from '#nestjs/microservices';
#Controller()
export class MathController {
#MessagePattern({ cmd: 'sum' })
accumulate(data: number[]): number {
return (data || []).reduce((a, b) => a + b);
}
}

This should work for you:
import { INestApplication } from '#nestjs/common';
import { Test, TestingModule } from '#nestjs/testing';
import { ClientsModule, Transport, ClientProxy } from '#nestjs/microservices';
import * as request from 'supertest';
import { Observable } from 'rxjs';
describe('Math e2e test', () => {
let app: INestApplication;
let client: ClientProxy;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [
MathModule,
ClientsModule.register([
{ name: 'MathService', transport: Transport.TCP },
]),
],
}).compile();
app = moduleFixture.createNestApplication();
app.connectMicroservice({
transport: Transport.TCP,
});
await app.startAllMicroservicesAsync();
await app.init();
client = app.get('MathService');
await client.connect();
});
afterAll(async () => {
await app.close();
client.close();
});
it('test accumulate', done => {
const response: Observable<any> = client.send(
{ cmd: 'sum' },
{ data: [1, 2, 3] },
);
response.subscribe(sum=> {
expect(sum).toBe(6);
done();
});
});
});

Related

Why does this test code fail (It works in the application) - using testing-library/react-hooks

I'm having trouble writing test code for this application. Specifically, the code works in the app - it works exactly the way I want it to. But with all the crazy mocks and abstractions in the codebase I have to work with, for some reason the tests never seem to pass.
Here is the code I'm hoping to test:
// useLandAccessHistory.ts
import { RequestState, RootState, useAppDispatch, useAppSelector } from '../../../../../store';
import { fetchLandAccessHistory } from '../../../../../extension/LandAccess/module/thunks';
import { LAND_ACCESS_SLICE_NAME } from '../../../../../extension/LandAccess/module/constants';
import { models } from '#h2know-how/moata-land-management-sdk';
import { useEffect, useMemo } from 'react';
const landAccessHistoryTableSelector = (state: RootState) => {
const landAccess = state[LAND_ACCESS_SLICE_NAME];
const byTitleId = (titleId: number): models.LandAccessHistory[] | undefined =>
landAccess.landAccessHistories[titleId];
const { status, error } = landAccess.apiStatus.fetchLandAccessHistory;
return { status, error, byTitleId };
};
export const useLandAccessHistory = ({
projectId,
titleId,
}: {
projectId: number;
titleId: number;
}): { historyData?: models.LandAccessHistory[] } & RequestState => {
const dispatch = useAppDispatch();
const { status, error, byTitleId } = useAppSelector(landAccessHistoryTableSelector);
useEffect(() => {
const req = dispatch(
fetchLandAccessHistory({
projectId,
titleId,
})
);
return req.abort;
}, [dispatch, projectId, titleId]);
const historyData = useMemo(() => byTitleId(titleId), [byTitleId, titleId]);
return { historyData, status, error };
};
And here is the test suite that is failing.
// useLandAccessHistory.spec.ts
import React from 'react';
import { Provider } from 'react-redux';
import { configureStore } from '#reduxjs/toolkit';
import { renderHook } from '#testing-library/react-hooks';
import { waitFor } from '#testing-library/react';
import { useLocation } from 'react-router-dom';
import { useLandAccessHistory } from './useLandAccessHistory';
import { useAppDispatch, useAppSelector } from '../../../../../store';
import { landAccessSlice } from '../../../../../extension/LandAccess/module/landAccess';
import { LAND_ACCESS_SLICE_NAME } from '../../../../../extension/LandAccess/module/constants';
import { useUrlParams, useUrlSearchParams } from '../../../../../utils/hooks';
import { mockLandAccessHistories } from '../../../../../extension/LandAccess/test-helpers/factories';
import { landAccessAPI } from '../../../../../extension/LandAccess/api/landAccessAPI';
const mockHistories = mockLandAccessHistories({ projectId: 123, titleId: 345 }, 2);
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: jest.fn(),
}));
jest.mock('../../../../../store');
jest.mock('../../../../../utils/hooks', () => ({
...jest.requireActual('../../../../../utils/hooks'),
useUrlParams: jest.fn(),
useUrlSearchParams: jest.fn(),
}));
jest.mock('../../../../../extension/LandAccess/api/landAccessAPI', () => ({
getLandAccessHistory: jest.fn(() => Promise.resolve(mockHistories)),
}));
describe('useLandAccessHistory', () => {
let dispatch: jest.Mock;
let abort: jest.Mock;
let store: any;
let wrapper: any;
beforeEach(() => {
(useLocation as jest.Mock).mockReturnValue({ pathname: '/route/123' });
store = configureStore({
reducer: { [LAND_ACCESS_SLICE_NAME]: landAccessSlice.reducer },
middleware: (getDefaultMiddleware) => getDefaultMiddleware(),
});
dispatch = jest.fn((...params) => store.dispatch(...params));
abort = jest.fn();
dispatch.mockReturnValue({ abort });
wrapper = ({ children }) => <Provider store={store}>{children}</Provider>;
(useUrlParams as jest.Mock).mockReturnValue({ projectId: 123 });
(useUrlSearchParams as jest.Mock).mockReturnValue({ feature_id: 345 });
(useAppDispatch as jest.Mock).mockReturnValue(dispatch);
(useAppSelector as jest.Mock).mockImplementation((selectorFunction) => selectorFunction(store.getState()));
});
it('fetches the land access history', async () => {
const { result, rerender } = renderHook(() => useLandAccessHistory({ projectId: 123, titleId: 345 }), { wrapper });
await waitFor(() => {
expect(dispatch).toHaveBeenCalled(); // this line runs.
});
rerender();
await waitFor(() => {
expect(store.getState().landAccessHistories).toEqual(mockHistories); // the store does not contain the data
expect(result.current).toEqual({ error: null, status: 'fulfilled', historyData: mockHistories }); // the status is idle and history data is undefined
});
});
});
Here's what I've tried so far: so far as I can tell, dispatch runs, but it doesn't seem to actually call the mocked (or real) API function. It could be that dispatch has been called but has not yet resolved, but then wouldn't await waitFor and rerender() help solve that problem?

Apollo Graphql with Angular with headers and Subscriptions

I need to add headers to my graphql requests with angular with subscriptions. but I didn't find any way. headers will be added if I only used headers without subscriptions. Also, subscriptions will works if I didn't add headers. But with both, it won't work. here is my code
import { NgModule } from '#angular/core';
import { HttpClientModule } from '#angular/common/http';
import { ApolloModule, Apollo, APOLLO_OPTIONS } from 'apollo-angular';
import { HttpLinkModule, HttpLink } from 'apollo-angular-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloLink, split } from 'apollo-link';
import { setContext } from 'apollo-link-context';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
const uri = 'http://localhost:5000/graphql';
export function provideApollo(httpLink: HttpLink) {
const basic = setContext((operation, context) => ({
headers: {
Accept: 'charset=utf-8'
}
}));
// Get the authentication token from local storage if it exists
const token = localStorage.getItem('token');
const auth = setContext((operation, context) => ({
headers: {
Authorization: `Bearer ${token}`
},
}));
const subscriptionLink = new WebSocketLink({
uri:
'ws://localhost:5000/graphql',
options: {
reconnect: true,
connectionParams: {
authToken: localStorage.getItem('token') || null
}
}
});
const link = split(({ query }) => {
const { kind } = getMainDefinition(query);
return kind === 'OperationDefinition';
}, subscriptionLink, ApolloLink.from([basic, auth, httpLink.create({ uri })]));
// const link = ApolloLink.from([basic, auth, httpLink.create({ uri }), subscriptionLink]);
const cache = new InMemoryCache();
return {
link,
cache
};
}
#NgModule({
exports: [
HttpClientModule,
ApolloModule,
HttpLinkModule
],
providers: [{
provide: APOLLO_OPTIONS,
useFactory: provideApollo,
deps: [HttpLink]
}]
})
export class GraphQLModule { }
in here headers will not be added. Any Solutions?
This is the solution that I found.
Import below code to your app.module.ts
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
import { HttpHeaders } from '#angular/common/http';
import { APOLLO_OPTIONS } from "apollo-angular";
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
import { split } from 'apollo-link';
import { InMemoryCache } from "apollo-cache-inmemory";
import { HttpLink } from 'apollo-angular-link-http';
#NgModule({
imports: [
CommonModule,
],
providers: [
{
provide: APOLLO_OPTIONS,
useFactory(httpLink: HttpLink) {
const http = httpLink.create({
uri: 'http://localhost/v1/graphql',
headers: new HttpHeaders({
"x-hasura-admin-secret": "mysecretkey"
})
})
const ws = new WebSocketLink({
uri: `ws://localhost/v1/graphql`,
options: {
reconnect: true,
connectionParams: {
headers: {
"x-hasura-admin-secret": "mysecretkey"
}
}
}
});
const link = split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
ws,
http,
);
return {
link,
cache: new InMemoryCache(),
};
},
deps: [HttpLink],
},
]
})
export class GraphqlModule { }
import { NgModule } from "#angular/core";
import { HttpClientModule, HttpHeaders } from "#angular/common/http";
import { ApolloModule, Apollo, APOLLO_OPTIONS } from "apollo-angular";
import { HttpLinkModule, HttpLink } from "apollo-angular-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
import { ApolloLink, split, from } from "apollo-link";
import { setContext } from "apollo-link-context";
import { WebSocketLink } from "apollo-link-ws";
import { getMainDefinition } from "apollo-utilities";
import ApolloClient from "apollo-client";
const uri = "http://localhost:5000/graphql";
const subscriptionLink = new WebSocketLink({
uri: "ws://localhost:5000/graphql",
options: {
reconnect: true,
connectionParams: {
authToken: localStorage.getItem("token") || null,
},
},
});
const authMiddleware = new ApolloLink((operation: any, forward: any) => {
operation.setContext({
headers: new HttpHeaders().set(
"Authorization",
`Bearer ${localStorage.getItem("token")}` || null,
),
});
return forward(operation);
});
export function createApollo(httpLink: HttpLink) {
return {
link: from([
authMiddleware,
split(
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === "OperationDefinition" && operation === "subscription";
},
subscriptionLink,
httpLink.create({
uri: "http://localhost:5000/graphql",
}),
),
]),
cache: new InMemoryCache(),
};
}
#NgModule({
exports: [HttpClientModule, ApolloModule, HttpLinkModule],
providers: [
{
provide: APOLLO_OPTIONS,
useFactory: createApollo,
deps: [HttpLink],
},
],
})
export class GraphQLModule {}

Connect NestJS to a websocket server

How can NestJS be use as a websocket client? I want to connect to a remote websocket server as a client using NestJS, but I didn't find any information about this implementation in the framework.
As Nestjs is simply a framework for Nodejs, so you need to find an NPM package that supports Websocket. For example, I use ws with #types/ws type definition, and create a Websocket client as a Nestjs service class:
// socket-client.ts
import { Injectable } from "#nestjs/common";
import * as WebSocket from "ws";
#Injectable()
export class WSService {
// wss://echo.websocket.org is a test websocket server
private ws = new WebSocket("wss://echo.websocket.org");
constructor() {
this.ws.on("open", () => {
this.ws.send(Math.random())
});
this.ws.on("message", function(message) {
console.log(message);
});
}
send(data: any) {
this.ws.send(data);
}
onMessage(handler: Function) {
// ...
}
// ...
}
// app.module.ts
import { Module } from "#nestjs/common";
import { WSService } from "./socket-client";
#Module({
providers: [WSService]
})
export class AppModule {}
I try it by another way. I write an adapter with socket.io-client. Then use this adapter in boostrap by method useWebSocketAdapter. After that i can write handle websocket event in gateway like the way working with socket server (use decorator #SubscribeMessage)
My Adapter file
import { WebSocketAdapter, INestApplicationContext } from '#nestjs/common';
import { MessageMappingProperties } from '#nestjs/websockets'
import * as SocketIoClient from 'socket.io-client';
import { isFunction, isNil } from '#nestjs/common/utils/shared.utils';
import { fromEvent, Observable } from 'rxjs';
import { filter, first, map, mergeMap, share, takeUntil } from 'rxjs/operators';
export class IoClientAdapter implements WebSocketAdapter {
private io;
constructor(private app: INestApplicationContext) {
}
create(port: number, options?: SocketIOClient.ConnectOpts) {
const client = SocketIoClient("http://localhost:3000" , options || {})
this.io = client;
return client;
}
bindClientConnect(server: SocketIOClient.Socket, callback: Function) {
this.io.on('connect', callback);
}
bindClientDisconnect(client: SocketIOClient.Socket, callback: Function) {
console.log("it disconnect")
//client.on('disconnect', callback);
}
public bindMessageHandlers(
client: any,
handlers: MessageMappingProperties[],
transform: (data: any) => Observable<any>,
) {
const disconnect$ = fromEvent(this.io, 'disconnect').pipe(
share(),
first(),
);
handlers.forEach(({ message, callback }) => {
const source$ = fromEvent(this.io, message).pipe(
mergeMap((payload: any) => {
const { data, ack } = this.mapPayload(payload);
return transform(callback(data, ack)).pipe(
filter((response: any) => !isNil(response)),
map((response: any) => [response, ack]),
);
}),
takeUntil(disconnect$),
);
source$.subscribe(([response, ack]) => {
if (response.event) {
return client.emit(response.event, response.data);
}
isFunction(ack) && ack(response);
});
});
}
public mapPayload(payload: any): { data: any; ack?: Function } {
if (!Array.isArray(payload)) {
return { data: payload };
}
const lastElement = payload[payload.length - 1];
const isAck = isFunction(lastElement);
if (isAck) {
const size = payload.length - 1;
return {
data: size === 1 ? payload[0] : payload.slice(0, size),
ack: lastElement,
};
}
return { data: payload };
}
close(server: SocketIOClient.Socket) {
this.io.close()
}
}
main.js
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
import {IoClientAdapter} from './adapters/ioclient.adapter'
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useWebSocketAdapter(new IoClientAdapter(app))
await app.listen(3006);
console.log(`Application is running on: ${await app.getUrl()}`);
}
bootstrap();
then Gateway
import {
MessageBody,
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
WsResponse,
} from '#nestjs/websockets';
import { from, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Server } from 'socket.io';
#WebSocketGateway()
export class EventsGateway {
#WebSocketServer()
server: Server;
#SubscribeMessage('hello')
async identity(#MessageBody() data: number): Promise<number> {
console.log(data)
return data;
}
}
It a trick, but look so cool. Message handler can write more like nestjs style.

Access to Apollo server for NestJS GraphQL test

A standard way to test an Apollo GraphQL server is to use the Apollo test client.
The createTestClient method requires a server argument.
In a NestJS/TypeGraphQL application, what's the appropriate way to access the Apollo server that's created by GraphQLModule from inside a (Jest) test?
const moduleFixture = await Test.createTestingModule({
imports: [ApplicationModule],
}).compile()
const app = await moduleFixture.createNestApplication(new ExpressAdapter(express)).init()
const module: GraphQLModule = moduleFixture.get<GraphQLModule>(GraphQLModule)
const apolloClient = createTestClient((module as any).apolloServer)
this is what i do
This code worked for me. Thanks to JustMe
import { Test, TestingModule } from '#nestjs/testing';
import { INestApplication } from '#nestjs/common';
import { createTestClient, TestQuery } from 'apollo-server-integration-testing';
import { AppModule } from './../src/app.module';
import { GraphQLModule } from '#nestjs/graphql';
describe('AppController (e2e)', () => {
let app: INestApplication;
// let mutateTest: TestQuery;
let correctQueryTest: TestQuery;
let wrongQueryTest: TestQuery;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
const module: GraphQLModule =
moduleFixture.get<GraphQLModule>(GraphQLModule);
const { query: correctQuery } = createTestClient({
apolloServer: (module as any).apolloServer,
extendMockRequest: {
headers: {
token:
'iIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWFiNmY0MjQ3YjEyYWNiNzQyYmQwYmYiLCJyb2xlIjoibWFuYWdlciIsImVtYWlsIjoibGVuYUBtYWlsLmNvbSIsInBhc3N3b3JkIjoiZTEwYWRjMzk0OWJhNTlhYmJlNTZlMDU3ZjIwZjg4M2UiLCJ1c2VybmFtZSI6ImxlbmEgZG9lIiwiY3JlYXRlZEF0IjoiMjAyMS0xMi0wNFQxMzozODoxMC4xMzZaIiwidXBkYXRlZEF0IjoiMjAyMS0xMi0wNFQxMzozODoxMC4xMzZaIiwiX192IjowLCJpYXQiOjE2Mzg2NTE4MjMsImV4cCI6MTYzODY1MTg4M30.d6SCh4x6Wwpj16UWf4ca-PbFCo1FQm_bLelp8kscG8U',
},
},
});
const { query: wrongQuery } = createTestClient({
apolloServer: (module as any).apolloServer,
});
// mutateTest = mutate;
correctQueryTest = correctQuery;
wrongQueryTest = wrongQuery;
});
it('/ Correct', async () => {
const result = await correctQueryTest(`
query FILTER_JOBS{
filterJobs(status: DONE) {
title,
status,
description,
lat,
long,
employees,
images,
assignedBy {
username,
email
}
}
}
`);
console.log(result);
});
it('/ Wrong', async () => {
const result = await wrongQueryTest(`
query FILTER_JOBS{
filterJobs(status: DONE) {
title,
status,
description,
lat,
long,
employees,
images,
assignedBy {
username,
email
}
}
}
`);
console.log(result);
});
});
After searching I ended using this:
import { getApolloServer } from '#nestjs/apollo';
import { INestApplication, ValidationPipe } from '#nestjs/common';
import { Test, TestingModule } from '#nestjs/testing';
import { ApolloServerBase, gql } from 'apollo-server-core';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
let apolloServer: ApolloServerBase<any>;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
app.useGlobalPipes(new ValidationPipe());
await app.init();
apolloServer = getApolloServer(app);
});
afterAll(async () => {
await app.close();
});
it('signUp', async () => {
const signUpInput = gql`
mutation Mutation($signUpInput: SignUpInput!) {
signup(signUpInput: $signUpInput) {
access
refresh
}
}
`;
const signUpResponse = await apolloServer.executeOperation({
query: signUpInput,
variables: {
signUpInput: {
name: 'John',
lastName: 'Doe',
email: 'test#gmail.com',
password: 'password',
},
},
});
expect(signUpResponse.data).toBeDefined();
});
});
This PR https://github.com/nestjs/graphql/pull/1104 enables you to write tests using apollo-server-testing.

Configuring subscriptions with NEXT.js and Apollo client 2

I am trying to configure subscriptions with Apollo 2 and NEXT.js. I can get the client to connect to the server and they are working in the GraphQL playground, so the bad configuration must be in the withData file, or the component that handles the subscription.
When inspecting the socket connection on the network panel in chrome, the subscription payload does not get added as a frame, like it does in the GraphQL playground.
withData:
import { ApolloLink, Observable } from 'apollo-link';
import { GRAPHQL_ENDPOINT, WS_PATH } from '../config/env';
import { ApolloClient } from 'apollo-client';
import { BatchHttpLink } from 'apollo-link-batch-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { SubscriptionClient } from 'subscriptions-transport-ws';
import { WebSocketLink } from 'apollo-link-ws';
import { createPersistedQueryLink } from 'apollo-link-persisted-queries';
import { onError } from 'apollo-link-error';
import withApollo from 'next-with-apollo';
import { withClientState } from 'apollo-link-state';
function createClient({ headers }) {
const cache = new InMemoryCache();
const request = async (operation) => {
operation.setContext({
http: {
includeExtensions: true,
includeQuery: false
},
headers
});
};
const requestLink = new ApolloLink(
(operation, forward) => new Observable((observer) => {
let handle;
Promise.resolve(operation)
.then(oper => request(oper))
.then(() => {
handle = forward(operation).subscribe({
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer)
});
})
.catch(observer.error.bind(observer));
return () => {
if (handle) handle.unsubscribe();
};
})
);
return new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
console.log({ graphQLErrors });
}
if (networkError) {
console.log('Logout user');
}
}),
requestLink,
// link,
withClientState({
defaults: {
isConnected: true
},
resolvers: {
Mutation: {
updateNetworkStatus: (_, { isConnected }, { cache }) => {
cache.writeData({ data: { isConnected } });
return null;
}
}
},
cache
}),
createPersistedQueryLink().concat(
new BatchHttpLink({
uri: GRAPHQL_ENDPOINT,
credentials: 'include'
}),
process.browser
? new WebSocketLink({
uri: WS_PATH,
options: {
reconnect: true
}
})
: null
)
]),
cache
});
}
export default withApollo(createClient);
Subscription component:
import { CONVERSATION_QUERY } from '../../constants/queries';
import { CONVERSATION_SUBSCRIPTION } from '../../constants/subscriptions';
import PropTypes from 'prop-types';
import { Query } from 'react-apollo';
const Conversation = props => (
<Query
{...props}
query={CONVERSATION_QUERY}
variables={{ input: { _id: props._id } }}
>
{(payload) => {
const more = () => payload.subscribeToMore({
document: CONVERSATION_SUBSCRIPTION,
variables: { input: { conversation: props._id } },
updateQuery: (prev, { subscriptionData }) => {
console.log({ subscriptionData });
if (!subscriptionData.data.messageSent) return prev;
const data = subscriptionData;
console.log({ data });
return Object.assign({}, prev, {});
},
onError(error) {
console.log(error);
},
onSubscriptionData: (data) => {
console.log('onSubscriptionData ', data);
}
});
return props.children({ ...payload, more });
}}
</Query>
);
Conversation.propTypes = {
children: PropTypes.func.isRequired
};
export default Conversation;
The subscription that has been tested in the GraphQL playground:
import gql from 'graphql-tag';
export const CONVERSATION_SUBSCRIPTION = gql`
subscription messageSent($input: messageSentInput) {
messageSent(input: $input) {
_id
users {
_id
profile {
firstName
lastName
jobTitle
company
picture
}
}
messages {
_id
body
createdAt
read
sender {
_id
profile {
firstName
lastName
jobTitle
company
picture
}
}
}
}
}
`;
The more function is then executed in componentDidMount:
componentDidMount() {
this.props.subscribeToMore();
}
The result in the console from the log in updateQuery is:
{"data":{"messageSent":null}}
I hadn't configured my withData file properly. You need to use split from the apollo-link package to let Apollo determine if the request should be handled with http or ws. Here is my working configuration file.
import { ApolloLink, Observable } from 'apollo-link';
import { ApolloClient } from 'apollo-client';
import { BatchHttpLink } from 'apollo-link-batch-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { WebSocketLink } from 'apollo-link-ws';
import { createPersistedQueryLink } from 'apollo-link-persisted-queries';
import { getMainDefinition } from 'apollo-utilities';
import { onError } from 'apollo-link-error';
import { split } from 'apollo-link';
import withApollo from 'next-with-apollo';
import { withClientState } from 'apollo-link-state';
import { GRAPHQL_ENDPOINT, WS_PATH } from '../config/env';
function createClient({ headers }) {
const cache = new InMemoryCache();
const request = async (operation) => {
operation.setContext({
http: {
includeExtensions: true,
includeQuery: false
},
headers
});
};
const requestLink = new ApolloLink(
(operation, forward) => new Observable((observer) => {
let handle;
Promise.resolve(operation)
.then(oper => request(oper))
.then(() => {
handle = forward(operation).subscribe({
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer)
});
})
.catch(observer.error.bind(observer));
return () => {
if (handle) handle.unsubscribe();
};
})
);
const httpLink = new BatchHttpLink({
uri: GRAPHQL_ENDPOINT
});
// Make sure the wsLink is only created on the browser. The server doesn't have a native implemention for websockets
const wsLink = process.browser
? new WebSocketLink({
uri: WS_PATH,
options: {
reconnect: true
}
})
: () => {
console.log('SSR');
};
// Let Apollo figure out if the request is over ws or http
const terminatingLink = split(
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return (
kind === 'OperationDefinition'
&& operation === 'subscription'
&& process.browser
);
},
wsLink,
httpLink
);
return new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
console.error({ graphQLErrors });
}
if (networkError) {
console.error({ networkError});
}
}),
requestLink,
// link,
withClientState({
defaults: {
isConnected: true
},
resolvers: {
Mutation: {
updateNetworkStatus: (_, { isConnected }, { cache }) => {
cache.writeData({ data: { isConnected } });
return null;
}
}
},
cache
}),
// Push the links into the Apollo client
createPersistedQueryLink().concat(
// New config
terminatingLink
// Old config
// new BatchHttpLink({
// uri: GRAPHQL_ENDPOINT,
// credentials: 'include'
// })
)
]),
cache
});
}
export default withApollo(createClient);

Resources