How to use GraphQL subscription correctly? - javascript

I have a GraphQL powered app. The query and mutation parts work well. I try to add GraphQL subscription.
The server GraphQL subscription part code is inspired by the demo in the readme of apollographql/subscriptions-transport-ws.
Please also check the comments in the code for more details.
import Koa from 'koa';
import Router from 'koa-router';
import graphqlHTTP from 'koa-graphql';
import asyncify from 'callback-to-async-iterator';
import { SubscriptionServer } from 'subscriptions-transport-ws';
import firebase from 'firebase-admin';
import { execute, subscribe } from 'graphql';
import { GraphQLObjectType, GraphQLString } from 'graphql';
const MeType = new GraphQLObjectType({
name: 'Me',
fields: () => ({
name: { type: GraphQLString },
// ...
}),
});
const listenMe = async (callback) => {
// Below the firebase API returns real-time data
return firebase
.database()
.ref('/users/123')
.on('value', (snapshot) => {
// snapshot.val() returns an Object including name field.
// Here I tested is correct, it always returns { name: 'Rose', ... }
// when some other fields inside got updated in database.
return callback(snapshot.val());
});
};
const Subscription = new GraphQLObjectType({
name: 'Subscription',
fields: () => ({
meChanged: {
type: MeType,
subscribe: () => asyncify(listenMe),
},
}),
});
const schema = new GraphQLSchema({
query: Query,
mutation: Mutation,
subscription: Subscription,
});
const app = new Koa();
app
.use(new Router()
.post('/graphql', async (ctx) => {
// ...
await graphqlHTTP({
schema,
graphiql: true,
})(ctx);
})
.routes());
const server = app.listen(3009);
SubscriptionServer.create(
{
schema,
execute,
subscribe,
},
{
server,
path: '/subscriptions',
},
);
I am using Altair GraphQL Client to test since it supports GraphQL subscription.
As the screenshot shows, it does get new data every time when the data changes in database.
However, meChanged is null and it does not throw any error. Any idea? Thanks

Finally have a new library can do the work without full Apollo framework.
https://github.com/enisdenjo/graphql-ws
Here are the codes that I have succeed:
Server (GraphQL Schema Definition Language)
import { useServer } from 'graphql-ws/lib/use/ws';
import WebSocket from 'ws';
import { buildSchema } from 'graphql';
const schema = buildSchema(`
type Subscription {
greeting: String
}
`);
const roots = {
subscription: {
greeting: async function* sayHiIn5Languages() {
for (const hi of ['Hi', 'Bonjour', 'Hola', 'Ciao', 'Zdravo']) {
yield { greeting: hi };
}
},
},
};
const wsServer = new ws.Server({
server, // Your HTTP server
path: '/graphql',
});
useServer(
{
schema,
execute,
subscribe,
roots,
},
wsServer
);
Server (GraphQL.js GraphQLSchema object way)
import { execute, subscribe, GraphQLObjectType, GraphQLSchema, GraphQLString } from 'graphql';
import { useServer } from 'graphql-ws/lib/use/ws';
import WebSocket from 'ws';
import { PubSub } from 'graphql-subscriptions';
const pubsub = new PubSub();
const subscription = new GraphQLObjectType({
name: 'Subscription',
fields: {
greeting: {
type: GraphQLString,
resolve: (source) => {
if (source instanceof Error) {
throw source;
}
return source.greeting;
},
subscribe: () => {
return pubsub.asyncIterator('greeting');
},
},
},
});
const schema = new GraphQLSchema({
query,
mutation,
subscription,
});
setInterval(() => {
pubsub.publish('greeting', {
greeting: 'Bonjour',
});
}, 1000);
const wsServer = new ws.Server({
server, // Your HTTP server
path: '/graphql',
});
useServer(
{
schema,
execute,
subscribe,
roots,
},
wsServer
);
Client
import { createClient } from 'graphql-ws';
const client = createClient({
url: 'wss://localhost:5000/graphql',
});
client.subscribe(
{
query: 'subscription { greeting }',
},
{
next: (data) => {
console.log('data', data);
},
error: (error) => {
console.error('error', error);
},
complete: () => {
console.log('no more greetings');
},
}
);
DISCLOSE: I am not associated with the library.

Related

req.query is undefined in Next.js API route

I'm trying to do a delete request. I can fetch the API route through pages/api/people/[something].js.
And this is the response I got from the browser's console.
DELETE - http://localhost:3000/api/people/6348053cad300ba679e8449c -
500 (Internal Server Error)
6348053cad300ba679e8449c is from the GET request at the start of the app.
In the Next.js docs, for example, the API route pages/api/post/[pid].js has the following code:
export default function handler(req, res) {
const { pid } = req.query
res.end(Post: ${pid})
}
Now, a request to /api/post/abc will respond with the text: Post: abc.
But from my API route pages/api/people/[something].js, something is undefined.
const { something } = req.query
UPDATED POST:
React component
export default function DatabaseTableContent(props) {
const id = props.item._id; // FROM A GET REQUEST
const hide = useWindowSize(639);
const [deletePeople] = useDeletePeopleMutation();
async function deleteHandler() {
await deletePeople(id);
}
return <Somecodes />;
}
apiSlice.js
export const apiSlice = createApi({
// reducerPath: "api",
baseQuery: fetchBaseQuery({ baseUrl: url }),
tagTypes: ["People"],
endpoints: (builder) => ({
getPeople: builder.query({
query: (people_id) => `/api/people/${people_id}`,
providesTags: ["People"],
}),
deletePeople: builder.mutation({
query: (studentInfo) => ({
url: `api/people/people-data/student-info/${studentInfo}`,
method: "DELETE",
headers: {
accept: "application/json",
},
}),
invalidatesTags: ["People"],
}),
}),
});
export const {
useGetPeopleQuery,
useDeletePeopleMutation,
} = apiSlice;
pages/api/people/people-data/student-info/[studentInfo].js
import { ObjectId, MongoClient } from "mongodb";
async function handler(res, req) {
const { studentInfo } = req.query; // the code stops here because "studentInfo" is undefined
const client = await MongoClient.connect(process.env.MONGODB_URI.toString());
const db = client.db("people-info");
if (req.method === "DELETE") {
try {
const deleteData = await db
.collection("student_info")
.deleteOne({ _id: ObjectId(studentInfo) });
const result = await res.json(deleteData);
client.close();
} catch (error) {
return res.status(500).json({ message: error });
}
}
}
export default handler;
The order of params passed to your handler functions needs to be reversed.
For NextJS API routes the req is the first param passed to the handler and the res param is second.
Example handler function from NextJS documentation:
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}

URQL WSS connection with GraphQL-WS says error 4500

import {
createClient,
defaultExchanges,dedupExchange, cacheExchange, fetchExchange,
subscriptionExchange,
gql
} from "#urql/core";
import { createClient as createWSClient } from "graphql-ws";
import { pipe, subscribe } from "wonka";
import { getToken, setToken } from "./helper";
const wsClient = createWSClient({
url: 'wss://**********/subscriptions',
reconnect: true,
});
const client = createClient({
url: "https://***********/",
fetchOptions: () => {
const token = getToken()
return token ? { headers: { authorization: `Bearer "${token}"` } } : {}
},
// the default:
exchanges: [
...defaultExchanges,
subscriptionExchange({
forwardSubscription(operation) {
return {
subscribe: (sink) => {
const dispose = wsClient.subscribe(operation, sink);
return {
unsubscribe: dispose,
};
},
};
},
}),
]
});
SUB_TO_MESSAGES = async () => {
console.log('sub')
const token = getToken();
console.log(String(token))
const { unsubscribe } = pipe(
await client.subscription(messageAdded,{ jwt: token }),
subscribe((result) => {
console.log(result)
})
)
};
I dont get the same issue with try and catch using GraphQL-WS but I still dont get any data from the server. The assignment is a vanillaJS project using GraphQL.I didndt post the url, jwt token,or the GET, POST, REgG as they work as intended. The rendering is done with a proxy. The error message is:
Connection Closed: 4500 Cannot read properties of undefined (reading 'Authorization')
Even playground doesnt work. Something wrong with the endpoint. It worked 2 weeks ago but admin says it still work yet I can find the problem. It used to work for me.
Here is the try and catch version:
import { createClient} from "graphql-ws";
import pStore from "./handler.js";
import { getToken } from "./helper";
const client = createClient({
url: "wss://******/subscriptions",
reconnect: true,
connectionParams:{
headers: {
"Authorization":`Bearer ${getToken()}`
}
},
})
async SUB_MESSAGE() {
try {
console.log('called Gql server')
const onNext = (res) => {
let obj = res.data.messageAdded
console.log(obj)
pStore[obj.id] = obj
pStore.render(obj)
};
let unsubscribe = () => {
/* complete the subscription */
};
new Promise((resolve, reject) => {
client.subscribe({
query: `subscription{messageAdded(jwt:"${getToken()}"){id text fromAgent createdAt updatedAt}}`,
},
{
next: (data)=> onNext(data),
error: reject,
complete: () => resolve(true),
})
})
}catch(error){
console.error('There has been a problem with your ws operation:', error);
}
}
Either way I think its a ad character, scope issue but I dont know where.

Jest custom axios interceptor

i am trying to use jest with nextJS to test API. I am using a custom interceptor for all http request to have authorization token on header. Here is my interceptor code
Api.ts
import axios from 'axios';
import config from '../config/index';
const Api = () => {
const defaultOptions = {
baseURL: config.APIENDPOINT,
method: 'get',
headers: {
'Content-Type': 'application/json',
},
};
// Create instance
let instance = axios.create(defaultOptions);
// Set the AUTH token for any request
instance.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
//#ts-ignore
config.headers.Authorization = token ? `${token}` : '';
return config;
});
instance.interceptors.response.use((res) => {
return res
});
return instance;
};
export default Api();
Here is the code to call the API
export const loadMerchants = async (id: any) => {
const data = await Api.get(config.APIENDPOINT + "/merchants/company/" + id)
console.log("data" ,data);
return (data)
}
And here is my test code
const axios = require('axios');
jest.mock('axios', () => {
return {
get: jest.fn(),
create: jest.fn(() => ({
interceptors: {
request: { use: jest.fn(() => Promise.resolve({ data: { foo: 'bar' } })) },
response: { use: jest.fn(() => Promise.resolve({ data: { foo: 'bar' } })) },
}
}))
}
})
it('Merchant API call', async () => {
axios.get.mockResolvedValue({
data: [
{
userId: 1,
id: 1,
title: 'My First Album'
},
{
userId: 1,
id: 2,
title: 'Album: The Sequel'
}
]
});
const merchants = await loadMerchants("1")
console.log(merchants) //always undefined
// expect(merchants).toEqual('some data');
});
on my API call if use axios.get instead of Api.get i get the correct results. I have looked into google and haven`t found any solutions.
Any help would be appreciated. Thank you.

unit-test with HTTP request returns Promise { <pending> }

I am using axios mock adapter to mock HTTP request to test my function. After I defined the behaviour for the function, and then I created an instance of the class to call the function, the result is
**Promise { <pending> }**,
what is the problem? how can I return the value I defined?
Here is my code:
UserService.js
export default class UserService {
getUserInfo = userId => {
const params = {
userId,
};
return axios
.get('https://www.usefortesting.com', {
params: { userId: params },
})
.then(response => response.data.userInfo)
.catch(error => error);
};
}
UserService.test.js
import React from 'react';
import axios from 'axios';
import UserService from './UserService';
import MockAdapter from 'axios-mock-adapter';
describe('testing', () => {
let axiosMock;
const Info = {
userInfo: {
id: '123',
name: 'omg',
},
};
beforeEach(function() {
axiosMock = new MockAdapter(axios);
});
afterEach(() => {
axiosMock.reset();
axiosMock.restore();
});
it('testing', () => {
axiosMock
.onGet('https://www.usefortesting.com', {
params: { userId: 'user_1' },
})
.reply(200, Info);
let userService = new UserService();
let response = userService.getUserInfo('user_1');
console.log(response);
});
});
You need to await for response in your test. Either use callbacks or async/await as shown below.
Your test should be like this:
it('testing', async () => { // notice async here
axiosMock
.onGet('https://www.usefortesting.com', {
params: { userId: 'user_1' },
})
.reply(200, Info);
let userService = new UserService();
let response = await userService.getUserInfo('user_1'); // notice await here
console.log(response);
});
OR
it('testing', () => {
...
userService.getUserInfo('user_1').then(response => {
console.log(response);
});
});
You can check this link on jest docs for more examples.
Also there is error in your getUserInfo() method, in params you are passing an object for userId but you need to pass string or int. What you should do is:
return axios.get('https://www.usefortesting.com', {
params: { userId: params.userId },
})...
OR
return axios.get('https://www.usefortesting.com', {
params,
})...

redux-mock-store store's state is not updating after dispatching actions

I have a logger middleware that shows actions about to be dispatched and next state.
I am writing tests for my action and in the mock store I am dispatching the actions. These successfully dispatch however the mock store state is not being updated (as shown by the aforementioned logger).
please note I am using redux-mock-store.
//authActionTest.js
it('creates LOGIN_SUCCESS when successful login has occured', ()=>{
//array of expected actions to be dispatched.
const expectedActions = [
{ type: constants.LOGIN_REQUEST },
{ type: 'LOGIN_SUCCESS',
payload: {
uid: '123abc',
role: 'clinician'
}}
]
const store = mockStore({ auth : {} })
return store.dispatch(actions.loginUser('abc#123.com', 'password123'))
expect(store.getActions()).toEqual(expectedActions)
})
The logger shows the following:
//logger
dispatching({ type: 'LOGIN_REQUEST })
next state { auth: {} }
dispatching({ type: 'LOGIN_SUCCESS',
payload: { uid: 123,
role: 'clinician'
})
next state { auth: {} }
You are trying to test an async action.
So to answer this question, we can follow what the redux docs are suggesting.
describe('async actions', () => {
afterEach(() => {
nock.cleanAll();
});
it('creates LOGIN_SUCCESS on successful login', () => {
nock('http://example.com/')
.get('/login')
.reply(200, payload: { uid: 123, role: 'clinician' });
const expectedActions = [
{ type: types.LOGIN_REQUEST },
{ type: types.LOGIN_SUCCESS, payload: { uid: 123, role: 'clinician' } },
];
const store = mockStore({ auth: {} });
return store.dispatch(actions.loginUser())
.then(() => { // return of async actions
expect(store.getActions()).toEqual(expectedActions);
});
});
});
You may need to do some modifications to the above code to make it working.
If you are using axios, I would suggest to use moxios package instead of nock.

Categories

Resources