How to add "directiveResolvers" to apollo-server-express middleware? - express

I'm working on transitioning from apollo-server to apollo-server-express, so that I can implement Stripe webhooks.
Previously with apollo-server, I was able to add directiveResolvers to the schema, so that I can perform an #auth check on queries or mutations. The server file looked like this:
apollo-server:
import { ApolloServer } from 'apollo-server';
import { ApolloServerPluginLandingPageGraphQLPlayground, ApolloServerPluginLandingPageDisabled } from 'apollo-server-core';
import { CorsOptions } from 'cors';
import { makeExecutableSchema } from '#graphql-tools/schema';
import { applyMiddleware } from 'graphql-middleware';
import { context } from './context';
import { resolvers } from './resolvers';
import typeDefs from './typeDefs';
import directiveResolvers from '#/directives/isAuthorized';
const corsOptions = {
origin: process.env.ORIGIN_URL,
credentials: true,
} as CorsOptions;
const graphqlEndpoint = 'graphql';
const schema = makeExecutableSchema({
typeDefs,
resolvers,
directiveResolvers,
});
const server = new ApolloServer({
schema: applyMiddleware(schema),
context,
cors: corsOptions,
introspection: true,
plugins: [
process.env.NODE_ENV === 'production'
? ApolloServerPluginLandingPageDisabled()
: ApolloServerPluginLandingPageGraphQLPlayground()
],
debug: true,
});
server
.listen({ port: process.env.PORT || 4000 })
.then(({ url }) => console.log(`🚀 Server ready at: ${url}${graphqlEndpoint}`));
With apollo-server-express though, I'm struggling to understand where to add these directiveResolvers in the configuration. Here is what the file currently looks like:
apollo-server-express:
import { ApolloServer } from 'apollo-server-express';
import express from 'express';
import http from 'http';
import {
ApolloServerPluginLandingPageGraphQLPlayground,
ApolloServerPluginLandingPageDisabled,
ApolloServerPluginDrainHttpServer
} from 'apollo-server-core';
import { CorsOptions } from 'cors';
import { makeExecutableSchema } from '#graphql-tools/schema';
import { applyMiddleware } from 'graphql-middleware';
import { context } from './context';
import { resolvers } from './resolvers';
import typeDefs from './typeDefs';
import directiveResolvers from '#/directives/isAuthorized';
const corsOptions: CorsOptions = {
origin: process.env.ORIGIN_URL,
credentials: true,
};
async function startApolloServer () {
const app = express();
const httpServer = http.createServer(app);
const server = new ApolloServer({
typeDefs,
resolvers,
context,
introspection: true,
plugins: [
ApolloServerPluginDrainHttpServer({ httpServer }),
process.env.NODE_ENV === 'production'
? ApolloServerPluginLandingPageDisabled()
: ApolloServerPluginLandingPageGraphQLPlayground()
],
});
await server.start();
server.applyMiddleware({ app, cors: corsOptions });
await new Promise<void>(resolve => httpServer.listen({ port: process.env.PORT || 4000 }, resolve));
console.log(`🚀 Server ready at: http://localhost:4000${server.graphqlPath}`);
}
startApolloServer();
How do I go about adding the directiveResolvers to the server configuration?

In classic fashion, I figured it out pretty soon after posting this. I'd missed the makeExecutableSchema piece that I'd used before. It now looks like this and seems to be functioning correctly:
import { ApolloServer } from 'apollo-server-express';
import express from 'express';
import http from 'http';
import {
ApolloServerPluginLandingPageGraphQLPlayground,
ApolloServerPluginLandingPageDisabled,
ApolloServerPluginDrainHttpServer
} from 'apollo-server-core';
import { CorsOptions } from 'cors';
import { makeExecutableSchema } from '#graphql-tools/schema';
import { context } from './context';
import { resolvers } from './resolvers';
import typeDefs from './typeDefs';
import directiveResolvers from '#/directives/isAuthorized';
const corsOptions: CorsOptions = {
origin: process.env.ORIGIN_URL,
credentials: true,
};
const schema = makeExecutableSchema({
typeDefs,
resolvers,
directiveResolvers,
});
async function startApolloServer () {
const app = express();
const httpServer = http.createServer(app);
const server = new ApolloServer({
schema,
context,
introspection: true,
plugins: [
ApolloServerPluginDrainHttpServer({ httpServer }),
process.env.NODE_ENV === 'production'
? ApolloServerPluginLandingPageDisabled()
: ApolloServerPluginLandingPageGraphQLPlayground()
],
});
await server.start();
server.applyMiddleware({ app, cors: corsOptions });
await new Promise<void>(resolve => httpServer.listen({ port: process.env.PORT || 4000 }, resolve));
console.log(`🚀 Server ready at: http://localhost:4000${server.graphqlPath}`);
}
startApolloServer();

Related

How to set authentications headers with Vue apollo and composition api?

I've build my app with Vite. I read many documents on web about the topic but I'm still very confused. I've a login form that send credentials to a protected view. When post the data I set the headers and store the Bearer token in the local storage.
The problem is that it doesn't work cause the Bearer token result equal to null.
Only when I logout the token is set in the headers.
That's how is the header when I log in
And here how it's set when I log out...
My main.js code is this:
import { createApp, provide, h } from "vue";
import {
ApolloClient,
createHttpLink,
InMemoryCache,
} from "#apollo/client/core";
import { DefaultApolloClient } from "#vue/apollo-composable";
import App from "./App.vue";
import router from "./router";
import { createPinia } from "pinia";
import { provideApolloClient } from "#vue/apollo-composable";
const authToken = localStorage.getItem("auth-token");
const httpLink = createHttpLink({
uri: "http://localhost/graphql",
headers: {
Authorization: "Bearer " + authToken,
},
});
const cache = new InMemoryCache();
const apolloClient = new ApolloClient({
link: httpLink,
cache,
});
provideApolloClient(apolloClient);
const app = createApp({
setup() {
provide(DefaultApolloClient, apolloClient);
},
render: () => h(App),
});
app
.use(router)
.use(createPinia())
.mount("#app");
and this is my routes.js
const router = createRouter({
history: createWebHistory(),
routes
})
router.beforeEach((to, from, next) => {
const requiresAuth = to.matched.some(record => record.meta.requiresAuth);
const isAuthenticated = localStorage.getItem('auth-token');
if(requiresAuth && isAuthenticated===null){
next('/auth/login');
}else {
next();
}
});
I'm surely making some mistakes in my main.js but I cannot understand what's wrong. I'm very confused :-/
Thanks to who'll be able to help me.
Try using a helper function to get the token from local storage; I'm using this method and it's working fine for me. To get your code more organized, create a separate folder to define the apollo client. Here is the code:
// apolloClient.ts
import { ApolloClient, InMemoryCache, HttpLink } from "#apollo/client/core";
function getHeaders() {
const headers: { Authorization?: string; "Content-Type"?: string } = {};
const token = localStorage.getItem("access-token");
if (token) {
headers["Authorization"] = `Bearer ${token}`;
}
headers["Content-Type"] = "application/json";
return headers;
}
// Create an http link:
const httpLink = new HttpLink({
uri: `${import.meta.env.VITE_API_URL}/graphql`,
fetch: (uri: RequestInfo, options: RequestInit) => {
options.headers = getHeaders();
return fetch(uri, options);
},
});
// Create the apollo client
export const apolloClient = new ApolloClient({
cache: new InMemoryCache(),
link: httpLink,
defaultOptions: {
query: {
errorPolicy: "all",
},
mutate: {
errorPolicy: "all",
},
},
});
Then you can use it in your main.ts like this:
// main.ts
import { createApp, h } from "vue";
import { provideApolloClient } from "#vue/apollo-composable";
import App from "./App.vue";
import { apolloClient } from "./apolloClient";
const app = createApp({
setup() {
provideApolloClient(apolloClient);
},
render: () => h(App),
});
app.mount("#app");

cors error preflight alow origin mismatch for apollo client and sever

Overview
I am trying to get authentications set up in Apollo but I keep running into this network error: CORS error PreflightAllowOriginMismatch. I have looked and tried so many solutions on the internet but nothing is working.
I have my client running on localhost:3000 and my server on localhost:4000.
Code
//client index.js
import React from 'react';
import { render } from 'react-dom';
import { ApolloProvider } from '#apollo/client';
import App from './App';
import { ApolloClient, createHttpLink, InMemoryCache } from '#apollo/client';
const httpLink = createHttpLink({
uri: 'http://localhost:4000/graphql',
credentials: 'include',
});
const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache()
});
render((
<ApolloProvider client={client}>
<App />
</ApolloProvider>
), document.getElementById('root')
);
//server index.js
const { ApolloServer } = require('apollo-server-express');
const express = require('express');
const cors = require('cors');
const schema = require('./schema');
const models = require('./sequelize/models');
const server = new ApolloServer({
schema,
context: req => ({
...req,
models,
})
});
const app = express();
var corsOptions = {
origin: 'http://localhost:3000/',
credentials: true // <-- REQUIRED backend setting
};
app.use(cors(corsOptions));
server.applyMiddleware({ app, cors: false });
app.listen({ port: 4000 });
I'm honestly lost at this point. I new to apollo and for the life of me cannot find what i am missing.
Although I am unsure about the security the following changes fixed my issue.
remove
origin: 'http://localhost:3000/',
credentials: true // <-- REQUIRED backend setting
};
app.use(cors(corsOptions));
update
server.applyMiddleware({ app, cors: {credentials: true, origin: true} });
the final file:
//server index.js
const { ApolloServer } = require('apollo-server-express');
const express = require('express');
const schema = require('./schema');
const models = require('./sequelize/models');
const server = new ApolloServer({
schema,
context: req => ({
...req,
models,
})
});
const app = express();
server.applyMiddleware({
app,
cors: {credentials: true, origin: true}
});
app.listen({ port: 4000 });

vue 3 Server Side Rendering with Vuex and Router

I have created a Vue3 application using the Vue CLI to create my application with Vuex and Router. The application runs well.
Note: I followed this useful doc for the Vuex with Vue3 https://blog.logrocket.com/using-vuex-4-with-vue-3/
Requirement Now I would like to change my Vue3 application to have Server Side Rendering support(i.e. SSR).
I watched this awesome video on creating an SSR application using Vue3 : https://www.youtube.com/watch?v=XJfaAkvLXyU and I can create and run a simple application like in the video. However I am stuck when trying to apply it to my main Vue3 app.
My current sticking point is how to specify the router and vuex on the server code.
My Code
The client entry file (src/main.js) has the following
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
createApp(App).use(store).use(router).mount('#app');
The server entry file (src/main.server.js) currently has the following
import App from './App.vue';
export default App;
And in the express server file (src/server.js) it currently has
const path = require('path');
const express = require('express');
const { createSSRApp } = require('vue');
const { renderToString } = require('#vue/server-renderer');
...
...
server.get('*', async (req, res) => {
const app = createSSRApp(App);
const appContent = await renderToString(app);
I need to change this code so that the app on the server side is using the router and vuex like it is on the client.
Issues
In the express server file i can not import the router and vuex like in the client entry file as it fails due to importing outside a module, therefore in the express server I can not do the following
const app = createSSRApp(App).use(store).use(router);
I have tried changing the server entry file (src/main.server.js) to the following, but this does not work either.
import App from './App.vue';
import router from './router';
import store from './store';
const { createSSRApp } = require('vue');
export default createSSRApp(App).use(store).use(router);
Does anyone know how to do SSR in Vue 3 when your app is using Vuex and Router.
How i did this in Vue 2 is below and what i am trying to change over to Vue 3
My Vue2 version of this application had the following code
src/app.js creates the Vue component with the router and store specified
Client entry file (src/client/main.js) gets the app from app.js, prepopulates the Vuex store with the data serialized out in the html, mounts the app when the router is ready
import Vue from 'vue';
import { sync } from 'vuex-router-sync';
import App from './pages/App.vue';
import createStore from './vuex/store';
import createRouter from './pages/router';
export default function createApp() {
const store = createStore();
const router = createRouter();
sync(store, router);
const app = new Vue({
router,
store,
render: (h) => h(App),
});
return { app, router, store };
}
Server Entry file (src/server/main.js), gets the app from app.js, get the matched routes which will call the "serverPrefetch" on each component to get its data populated in the Vuex store, then returns the resolve promise
import createApp from '../app';
export default (context) => new Promise((resolve, reject) => {
const { app, router, store } = createApp();
router.push(context.url);
router.onReady(() => {
const matchedComponents = router.getMatchedComponents();
if (!matchedComponents.length) {
return reject(new Error('404'));
}
context.rendered = () => {
context.state = store.state;
};
return resolve(app);
}, reject);
});
Express server (/server.js) uses the bundle renderer to render the app to a string to put in the html
const fs = require('fs');
const express = require('express');
const { createBundleRenderer } = require('vue-server-renderer');
const dotenv = require('dotenv');
dotenv.config();
const bundleRenderer = createBundleRenderer(
require('./dist/vue-ssr-server-bundle.json'),
{
template: fs.readFileSync('./index.html', 'utf-8'),
},
);
const server = express();
server.use(express.static('public'));
server.get('*', (req, res) => {
const context = {
url: req.url,
clientBundle: `client-bundle.js`,
};
bundleRenderer.renderToString(context, (err, html) => {
if (err) {
if (err.code === 404) {
res.status(404).end('Page not found');
} else {
res.status(500).end('Internal Server Error');
}
} else {
res.end(html);
}
});
});
const port = process.env.PORT || 3000
server.listen(port, () => {
console.log(`Listening on port ${port}`);
});
I have managed to find the solution to this thanks to the following resources:
Server Side Rendering with Vue.js 3 video: https://www.youtube.com/watch?v=XJfaAkvLXyU&feature=youtu.be and git repos: https://github.com/moduslabs/vue3-example-ssr
SSR + Vuex + Router app : https://github.com/shenron/vue3-example-ssr
migrating from Vue 2 to Vue 3
https://v3-migration.vuejs.org/breaking-changes/introduction.html
migrating from VueRouter 3 to VueRouter 4
https://next.router.vuejs.org/guide/migration/
migrating from Vuex 3 to Vuex 4
https://next.vuex.vuejs.org/guide/migrating-to-4-0-from-3-x.html
client entry file (src/main.js)
import buildApp from './app';
const { app, router, store } = buildApp();
const storeInitialState = window.INITIAL_DATA;
if (storeInitialState) {
store.replaceState(storeInitialState);
}
router.isReady()
.then(() => {
app.mount('#app', true);
});
server entry file (src/main-server.js)
import buildApp from './app';
export default (url) => new Promise((resolve, reject) => {
const { router, app, store } = buildApp();
// set server-side router's location
router.push(url);
router.isReady()
.then(() => {
const matchedComponents = router.currentRoute.value.matched;
// no matched routes, reject with 404
if (!matchedComponents.length) {
return reject(new Error('404'));
}
// the Promise should resolve to the app instance so it can be rendered
return resolve({ app, router, store });
}).catch(() => reject);
});
src/app.js
import { createSSRApp, createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
const isSSR = typeof window === 'undefined';
export default function buildApp() {
const app = (isSSR ? createSSRApp(App) : createApp(App));
app.use(router);
app.use(store);
return { app, router, store };
}
server.js
const serialize = require('serialize-javascript');
const path = require('path');
const express = require('express');
const fs = require('fs');
const { renderToString } = require('#vue/server-renderer');
const manifest = require('./dist/server/ssr-manifest.json');
// Create the express app.
const server = express();
// we do not know the name of app.js as when its built it has a hash name
// the manifest file contains the mapping of "app.js" to the hash file which was created
// therefore get the value from the manifest file thats located in the "dist" directory
// and use it to get the Vue App
const appPath = path.join(__dirname, './dist', 'server', manifest['app.js']);
const createApp = require(appPath).default;
const clientDistPath = './dist/client';
server.use('/img', express.static(path.join(__dirname, clientDistPath, 'img')));
server.use('/js', express.static(path.join(__dirname, clientDistPath, 'js')));
server.use('/css', express.static(path.join(__dirname, clientDistPath, 'css')));
server.use('/favicon.ico', express.static(path.join(__dirname, clientDistPath, 'favicon.ico')));
// handle all routes in our application
server.get('*', async (req, res) => {
const { app, store } = await createApp(req);
let appContent = await renderToString(app);
const renderState = `
<script>
window.INITIAL_DATA = ${serialize(store.state)}
</script>`;
fs.readFile(path.join(__dirname, clientDistPath, 'index.html'), (err, html) => {
if (err) {
throw err;
}
appContent = `<div id="app">${appContent}</div>`;
html = html.toString().replace('<div id="app"></div>', `${renderState}${appContent}`);
res.setHeader('Content-Type', 'text/html');
res.send(html);
});
});
const port = process.env.PORT || 8080;
server.listen(port, () => {
console.log(`You can navigate to http://localhost:${port}`);
});
vue.config.js
used to specify the webpack build things
const ManifestPlugin = require('webpack-manifest-plugin');
const nodeExternals = require('webpack-node-externals');
module.exports = {
devServer: {
overlay: {
warnings: false,
errors: false,
},
},
chainWebpack: (webpackConfig) => {
webpackConfig.module.rule('vue').uses.delete('cache-loader');
webpackConfig.module.rule('js').uses.delete('cache-loader');
webpackConfig.module.rule('ts').uses.delete('cache-loader');
webpackConfig.module.rule('tsx').uses.delete('cache-loader');
if (!process.env.SSR) {
// This is required for repl.it to play nicely with the Dev Server
webpackConfig.devServer.disableHostCheck(true);
webpackConfig.entry('app').clear().add('./src/main.js');
return;
}
webpackConfig.entry('app').clear().add('./src/main-server.js');
webpackConfig.target('node');
webpackConfig.output.libraryTarget('commonjs2');
webpackConfig.plugin('manifest').use(new ManifestPlugin({ fileName: 'ssr-manifest.json' }));
webpackConfig.externals(nodeExternals({ allowlist: /\.(css|vue)$/ }));
webpackConfig.optimization.splitChunks(false).minimize(false);
webpackConfig.plugins.delete('hmr');
webpackConfig.plugins.delete('preload');
webpackConfig.plugins.delete('prefetch');
webpackConfig.plugins.delete('progress');
webpackConfig.plugins.delete('friendly-errors');
// console.log(webpackConfig.toConfig())
},
};
src/router/index.js
import { createRouter, createMemoryHistory, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';
const isServer = typeof window === 'undefined';
const history = isServer ? createMemoryHistory() : createWebHistory();
const routes = [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/about',
name: 'About',
component: About,
},
];
const router = createRouter({
history,
routes,
});
export default router;
src/store/index.js
import Vuex from 'vuex';
import fetchAllBeers from '../data/data';
export default Vuex.createStore({
state() {
return {
homePageData: [],
};
},
actions: {
fetchHomePageData({ commit }) {
return fetchAllBeers()
.then((data) => {
commit('setHomePageData', data.beers);
});
},
},
mutations: {
setHomePageData(state, data) {
state.homePageData = data;
},
},
});
Github sample code
I found I needed to go through the building the code step by step doing just SSR, just Router, just Vuex and then put it all together.
My test apps are in github
https://github.com/se22as/vue-3-with-router-basic-sample
"master" branch : just a vue 3 app with a router
"added-ssr" branch : took the "master" branch and added ssr code
"add-just-vuex" branch : took the "master" branch and added vuex code
"added-vuex-to-ssr" branch : app with router, vuex and ssr.
You can also use Vite which has native SSR support and, unlike Webpack, works out-of-the-box without configuration.
And if you use vite-plugin-ssr then it's even easier.
The following highlights the main parts of vite-plugin-ssr's Vuex example
<template>
<h1>To-do List</h1>
<ul>
<li v-for="item in todoList" :key="item.id">{{item.text}}</li>
</ul>
</template>
<script>
export default {
serverPrefetch() {
return this.$store.dispatch('fetchTodoList');
},
computed: {
todoList () {
return this.$store.state.todoList
}
},
}
</script>
import Vuex from 'vuex'
export { createStore }
function createStore() {
const store = Vuex.createStore({
state() {
return {
todoList: []
}
},
actions: {
fetchTodoList({ commit }) {
const todoList = [
{
id: 0,
text: 'Buy milk'
},
{
id: 1,
text: 'Buy chocolate'
}
]
return commit('setTodoList', todoList)
}
},
mutations: {
setTodoList(state, todoList) {
state.todoList = todoList
}
}
})
return store
}
import { createSSRApp, h } from 'vue'
import { createStore } from './store'
export { createApp }
function createApp({ Page }) {
const app = createSSRApp({
render: () => h(Page)
})
const store = createStore()
app.use(store)
return { app, store }
}
import { renderToString } from '#vue/server-renderer'
import { html } from 'vite-plugin-ssr'
import { createApp } from './app'
export { render }
export { addContextProps }
export { setPageProps }
async function render({ contextProps }) {
const { appHtml } = contextProps
return html`<!DOCTYPE html>
<html>
<body>
<div id="app">${html.dangerouslySetHtml(appHtml)}</div>
</body>
</html>`
}
async function addContextProps({ Page }) {
const { app, store } = createApp({ Page })
const appHtml = await renderToString(app)
const INITIAL_STATE = store.state
return {
INITIAL_STATE,
appHtml
}
}
function setPageProps({ contextProps }) {
const { INITIAL_STATE } = contextProps
return { INITIAL_STATE }
}
import { getPage } from 'vite-plugin-ssr/client'
import { createApp } from './app'
hydrate()
async function hydrate() {
const { Page, pageProps } = await getPage()
const { app, store } = createApp({ Page })
store.replaceState(pageProps.INITIAL_STATE)
app.mount('#app')
}
Simplest example: Updated with the latest document on the Vue website.
https://github.com/ThinhVu/vue--just-ssr
You can find more examples in this repository which included more about impl SSR in the real world.
The repository not only includes naive implement in VueJs but also contains an example using Vite only, Vite + Vite-SSR-plugin, Nuxt, QuasarJS
(I'm working on it, more examples will be added later).
https://github.com/ThinhVu/vue-ssr-labs

How can i use GraphQl subscriptions in react-native chat application to get real-time updates from GraphQl queries

I am using GraphQl APIs in the react-native chat application. I want to get real-time updates when another user sends a message to me without refreshing the API. How can I do it using GraphQl API using GraphQl subscriptions or Websocket in react-native?
Should I use different URLs for subscription and normal API's?
Here is my config.js
import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { WebSocketLink } from 'apollo-link-ws';
import { HttpLink } from 'apollo-boost';
import { setContext } from 'apollo-link-context';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { AsyncStorage } from 'react-native';
// const httpLink = createHttpLink({
// uri: 'https://graphql.chat.dev.com/graphql',
// });
// const link = new HttpLink({
// uri: `https://graphql.chat.dev.com/graphql`,
// headers: {
// Authorization: AsyncStorage.getItem('#user_token');
// }
// });
const link = new WebSocketLink({
uri: `wss://graphql.chat.dev.com/graphql`,
options: {
reconnect: true,
connectionParams: {
headers: {
Authorization: AsyncStorage.getItem('#user_token');
}
}
}
})
const defaultOptions = {
query: {
fetchPolicy: "network-only",
errorPolicy: "all"
}
};
const client = new ApolloClient({
link: link,
cache: new InMemoryCache(),
defaultOptions
});
export default client;
I've not implemented Apollo with React Native but I did it with my React app. In my experience, you should use different URLs for subscription and normal APIs. Then, use import { split } from 'apollo-link' to split links, so you can send data to each link
depending on what kind of operation is being sent. You can read more about subscription in Apollo here.
This is my client.js file. Hopefully, it can help you.
import { ApolloClient } from 'apollo-client'
import { createUploadLink } from 'apollo-upload-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { setContext } from 'apollo-link-context'
import { split } from 'apollo-link'
import { WebSocketLink } from 'apollo-link-ws'
import { getMainDefinition } from 'apollo-utilities'
const getToken = () => localStorage.getItem('AUTH_TOKEN')
const APOLLO_SERVER ="APOLLO_SERVER url"
const APOLLO_SOCKET ="APOLLO_SOCKET url"
// Create an http link:
const httpLink = createUploadLink({
uri: APOLLO_SERVER,
credentials: 'same-origin',
})
const authLink = setContext((_, { headers }) => {
const token = getToken()
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
},
}
})
// Create a WebSocket link:
const wsLink = new WebSocketLink({
uri: APOLLO_SOCKET,
options: {
reconnect: true,
connectionParams: {
Authorization: getToken() ? `Bearer ${getToken()}` : '',
},
},
})
// using the ability to split links, you can send data to each link
// depending on what kind of operation is being sent
const link = split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query)
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
)
},
wsLink,
authLink.concat(httpLink)
)
const cache = new InMemoryCache()
const client = new ApolloClient({
cache,
link,
typeDefs,
resolvers,
})
This is my component where I integrate queries with subscriptions:
import React, { useEffect } from 'react'
import { useQuery } from '#apollo/react-hooks'
import gql from 'graphql-tag'
...
// query for querying message list
const GET_MESSAGE_LIST = gql`...`
// subscription for listening new message
const ON_MESSAGE_CREATED = gql`...`
const ChatView = props => {
const { data, loading, subscribeToMore } = useQuery(GET_MESSAGE_LIST, {
{
notifyOnNetworkStatusChange: true,
variables: {
query: {
limit: 10,
userId: props.userId,
},
},
}
})
useEffect(() => {
subscribeToMore({
document: ON_MESSAGE_CREATED,
variables: { filter: { userId: props.userId } },
shouldResubscribe: true,
updateQuery: (prev, { subscriptionData }) => {
let newMessage = subscriptionData.data.onZaloMessageCreated
return Object.assign({}, prev, {
messageList: {
...prev.messageList,
items:
prev.messageList.items.filter(
item => item.id === newMessage.id
).length === 0
? [newMessage, ...prev.messageList.items]
: prev.messageList.items,
},
})
},
})
}, [subscribeToMore])
return ...
}

apollo client , adding subscriptions while not breaking http link that uses jwt

I currently have a graphql api that handles HTTP requests, I've migrated to apollo-client and now I want to add subscriptions. The problem is, that I can't figure out how to combine my HTTP link (that has a JWT auth middleware).
I have no code errors to share, BUT, the issue is in the way i use the link split method. why? because when I remove it and just use authLink.concat(httpLink) everything works smoothly (except for that in this case I don't check if the connection is WS or HTTP...).
Here's my code:
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
// import ApolloClient from "apollo-boost"; // migrated to apollo-client
import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { InMemoryCache } from 'apollo-cache-inmemory';
// subscriptions imports
import { split } from 'apollo-link'
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
import VueApollo from "vue-apollo";
// links composition
const httpLink = createHttpLink({
uri: 'http://localhost:4000/graphql',
});
const wsLink = new WebSocketLink({
uri: `ws://localhost:5000`,
options: {
reconnect: true
}
});
// this is the sketchy section
const link = split(
({ query }) => {
const { kind, operation } = getMainDefinition(query)
return kind === 'OperationDefinition' && operation === 'subscription'
},
wsLink,
httpLink
)
// JWT middleware
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem('token');
return {
headers: {
...headers,
authorization: token ? token : ''
}
}
});
const wsPlusHttpWithJWT = authLink.concat(link)
export const apolloClient = new ApolloClient({
// link: authLink.concat(httpLink), // this worked
link: wsPlusHttpWithJWT,
cache: new InMemoryCache()
});
Vue.use(VueApollo);
const apolloProvider = new VueApollo({ defaultClient: apolloClient });
Vue.config.productionTip = false;
new Vue({
apolloProvider,
router,
render: h => h(App),
}).$mount("#app");