Issue setting up subscription with GraphQL - node.js

Good day:
I"m trying to setup my graphql server for a subscription. This is my schema.js
const ChatCreatedSubscription = new GraphQLObjectType({
name: "ChatCreated",
fields: () => ({
chatCreated: {
subscribe: () => pubsub.asyncIterator(CONSTANTS.Websocket.CHANNEL_CONNECT_CUSTOMER)
}
})
});
const ChatConnectedSubscription = {
chatConnected: {
subscribe: withFilter(
(_, args) => pubsub.asyncIterator(`${args.id}`),
(payload, variables) => payload.chatConnect.id === variables.id,
)
}
}
const subscriptionType = new GraphQLObjectType({
name: "Subscription",
fields: () => ({
chatCreated: ChatCreatedSubscription,
chatConnected: ChatConnectedSubscription
})
});
const schema = new GraphQLSchema({
subscription: subscriptionType
});
However, I'm getting this error when I try to run my subscription server:
ERROR introspecting schema: [
{
"message": "The type of Subscription.chatCreated must be Output Type but got: undefined."
},
{
"message": "The type of Subscription.chatConnected must be Output Type but got: undefined."
}
]

A field definition is an object that includes these properties: type, args, description, deprecationReason and resolve. All these properties are optional except type. Each field in your field map must be an object like this -- you cannot just set the field to a type like you're doing.
Incorrect:
const subscriptionType = new GraphQLObjectType({
name: "Subscription",
fields: () => ({
chatCreated: ChatCreatedSubscription,
chatConnected: ChatConnectedSubscription
})
});
Correct:
const subscriptionType = new GraphQLObjectType({
name: "Subscription",
fields: () => ({
chatCreated: {
type: ChatCreatedSubscription,
},
chatConnected: {
type: ChatConnectedSubscription,
},
})
});
Check the docs for additional examples.

Related

Unable to get initial data using graphql-ws subscription

I am fairly new to using graphql-ws and graphql-yoga server, so forgive me if this is a naive question or mistake from my side.
I went through graphql-ws documentation. It has written the schema as a parameter. Unfortunately, the schema definition used in the documentation is missing a reference.
After adding a new todo (using addTodo) it shows two todo items. So I believe it is unable to return the initial todo list whenever running subscribe on Yoga Graphiql explorer.
It should show the initial todo item as soon as it has been subscribed and published in the schema definition.
My understanding is there is something I am missing in the schema definition which is not showing the todo list when tried accessing Yoga Graphiql explorer.
Has anyone had a similar experience and been able to resolve it? What I am missing?
Libraries used
Backend
graphql-yoga
ws
graphql-ws
Frontend
solid-js
wonka
Todo item - declared in schema
{
id: "1",
title: "Learn GraphQL + Solidjs",
completed: false
}
Screenshot
Code Snippets
Schema definition
import { createPubSub } from 'graphql-yoga';
import { Todo } from "./types";
let todos = [
{
id: "1",
title: "Learn GraphQL + Solidjs",
completed: false
}
];
// channel
const TODOS_CHANNEL = "TODOS_CHANNEL";
// pubsub
const pubSub = createPubSub();
const publishToChannel = (data: any) => pubSub.publish(TODOS_CHANNEL, data);
// Type def
const typeDefs = [`
type Todo {
id: ID!
title: String!
completed: Boolean!
}
type Query {
getTodos: [Todo]!
}
type Mutation {
addTodo(title: String!): Todo!
}
type Subscription {
todos: [Todo!]
}
`];
// Resolvers
const resolvers = {
Query: {
getTodos: () => todos
},
Mutation: {
addTodo: (_: unknown, { title }: Todo) => {
const newTodo = {
id: "" + (todos.length + 1),
title,
completed: false
};
todos.push(newTodo);
publishToChannel({ todos });
return newTodo;
},
Subscription: {
todos: {
subscribe: () => {
const res = pubSub.subscribe(TODOS_CHANNEL);
publishToChannel({ todos });
return res;
}
},
},
};
export const schema = {
resolvers,
typeDefs
};
Server backend
import { createServer } from "graphql-yoga";
import { WebSocketServer } from "ws";
import { useServer } from "graphql-ws/lib/use/ws";
import { schema } from "./src/schema";
import { execute, ExecutionArgs, subscribe } from "graphql";
async function main() {
const yogaApp = createServer({
schema,
graphiql: {
subscriptionsProtocol: 'WS', // use WebSockets instead of SSE
},
});
const server = await yogaApp.start();
const wsServer = new WebSocketServer({
server,
path: yogaApp.getAddressInfo().endpoint
});
type EnvelopedExecutionArgs = ExecutionArgs & {
rootValue: {
execute: typeof execute;
subscribe: typeof subscribe;
};
};
useServer(
{
execute: (args: any) => (args as EnvelopedExecutionArgs).rootValue.execute(args),
subscribe: (args: any) => (args as EnvelopedExecutionArgs).rootValue.subscribe(args),
onSubscribe: async (ctx, msg) => {
const { schema, execute, subscribe, contextFactory, parse, validate } =
yogaApp.getEnveloped(ctx);
const args: EnvelopedExecutionArgs = {
schema,
operationName: msg.payload.operationName,
document: parse(msg.payload.query),
variableValues: msg.payload.variables,
contextValue: await contextFactory(),
rootValue: {
execute,
subscribe,
},
};
const errors = validate(args.schema, args.document);
if (errors.length) return errors;
return args;
},
},
wsServer,
);
}
main().catch((e) => {
console.error(e);
process.exit(1);
});
apply these changes
Mutation: {
addTodo: (_: unknown, { title }: Todo) => {
const newTodo = {
id: "" + (todos.length + 1),
title,
completed: false
};
todos.push(newTodo);
publishToChannel({ todos });
return newTodo;
},
Subscription: {
todos: {
subscribe: () => {
return Repeater.merge(
[
new Repeater(async (push, stop) => {
push({ todos });
await stop;
}),
pubSub.subscribe(TODOS_CHANNEL),
]
)
}
},
},
first, npm i #repeaterjs/repeater then import Repeater

graphql mutation giving error while creating record

here is my scheme, I wasnt to create one record so when i am passing field in graphql it is showing error
const { BookTC } = require("../model/book");
const { BookSchema } = require("../model/book");
BookTC.addResolver({
name: "create",
kind: "mutation",
type: BookTC.getResolver("createOne").getType(),
args: BookTC.getResolver("createOne").getArgs(),
resolve: async ({ source, args, context, info }) => {
const book = await BookSchema.create(args.record);
return {
record: book,
recordId: BookTC.getRecordIdFn()(book),
};
},
});
const BookMutation = {
bookWithFile: BookTC.getResolver("create"),
bookCreateOne: BookTC.getResolver("createOne"),
bookCreateMany: BookTC.getResolver("createMany")
};
module.exports = { BookQuery: BookQuery, BookMutation: BookMutation };

Error GraphQL "The type of Query.GetUser must be Output Type but got: undefined."

Writing my First Graphql and came across this problem.
Data is not showing
index.js:
const express = require('express')
const app = express()
const graphql = require('graphql')
const {graphqlHTTP} = require('express-graphql')
const { GetUser , mutation } = require('./schema')
const { DataType } = require('./DataType')
app.use(express.json())
const schema = new graphql.GraphQLSchema({
query: new graphql.GraphQLObjectType({
name:"Query",
type:new graphql.GraphQLList(DataType),
fields:{GetUser}
}),
mutation:new graphql.GraphQLObjectType({
name:"Mutation",
fields:{mutation}
})
})
app.use('/graphql',graphqlHTTP({
schema,
graphiql:true
}))
app.listen(1200,()=>{
console.log("SERVER AT 1200");
})
schema.js:
const graphql = require('graphql')
const {GraphQLObjectType , GraphQLString} = graphql
const {DataType} = require('./DataType')
var data = [
{
"name":"Book1",
"price":123
},
{
"name":"Book2",
"price":1232
},
{
"name":"Book3",
"price":2021
},
]
exports.GetUser = new GraphQLObjectType({
name:"GetAllUser",
type:new graphql.GraphQLList(DataType),
fileds:{
getAllUser:{
name:"getUser",
type:new graphql.GraphQLList(DataType),
resolve:()=>{
return data;
}
}
}
})
exports.mutation = new GraphQLObjectType({
name:"AddData",
fields:{
adduser:{
name:"adduser",
type:data,
args:{
name:{type:GraphQLString},
price:{type:graphql.GraphQLInt}
},
resolve:(_,args)=>{
data.push(args)
return args
}
}
}
})
DataType.js:
const graphql = require('graphql')
exports.DataType = new graphql.GraphQLObjectType({
name:"DataType",
fields:()=>({
name:{
type: graphql.GraphQLString
},
price:{
type:graphql.GraphQLInt
}
})
})
Running graphql:
query{
GetUser{
name
}
}
Error i get :
{
"errors": [
{
"message": "The type of Query.GetUser must be Output Type but got: undefined."
},
{
"message": "The type of Mutation.mutation must be Output Type but got: undefined."
}
]
}
Error says The type of Query.GetUser must be Output Type but got: undefined.
I defined the type of the query again but still shows same error same for mutation .
Also this was my first day of Graphql .

GraphQL Resolver for Interface on same Mongoose collection

I'm creating a GraphQL server that uses Mongoose and GraphQLInterfaceType. I have a GraphQLInterfaceType of Books and sub types of SchoolBooksType and ColoringBookType. in my Mongoose Schema I specified that both SchoolBooks and ColoringBooks are to be stored in the same books collection
const coloringSchema = new Schema({
title: String,//Interface
pages: String
});
module.exports = mongoose.model("ColoringBook", coloringSchema , "books");
const schoolSchema = new Schema({
title: String, //Interface
subject: String
});
module.exports = mongoose.model("SchoolBook", schoolSchema , "books");
Here is one of my types
const SchoolBookType = new GraphQLObjectType({
name: "SchoolBook",
interfaces: [BooksInterface],
isTypeOf: obj => obj instanceof SchoolBook,
fields: () => ({
title: { type: GraphQLString },
subject: { type: GraphQLString }
})
});
Here is my query: But I don't know what to return, if I need to combine the two collections into the same array?
books: {
type: new GraphQLList(BooksInterface),
resolve() {
return SchoolBook.find({}) //<---- What to return?
}
}
Here is my query:
{
books{
title
... on ColoringBook{
pages
}
... on SchoolBook{
subject
}
}
}
Any help would be great, Thank you.
I guess you can use an async resolver, and concat both queries.
resolve: async () => {
const schoolBooks = SchoolBook.find({}).exec()
const coloringBooks = ColoringBook.find({}).exec()
const [sbooks, cbooks] = await Promise.all([schoolBooks, coloringBooks])
return [...sbooks, ...cbooks]
}

GraphQL - passing an ObjectType a parameter

I'm using GraphQL and it's working great, however, I can't seem to figure out how to pass a parameter into the fields section of my Event GraphQLObjectType.
I would like to be able to pass in the currentUserId (which is given to me through a token) into the Event GraphQLObjectType so I can add in an isAttending attribute.
I've attached code with comments of what I'm basically trying to do:
const Event = new GraphQLObjectType({
name: 'Event',
description: 'This represents an Event',
fields: (currentUserId) => { // currentUserId is the parameter I would like to pass in
return {
id: {
type: GraphQLInt,
resolve (event) {
return event.id;
}
},
title: {
type: GraphQLString,
resolve (event) {
return event.title;
}
},
attendees: {
type: new GraphQLList(User),
resolve (event) {
return event.getAttendees()
}
},
// this is what I would like to do
isAttending: {
type: GraphQLBool,
resolve (event) {
return event.getAttendees({
where: {
id: currentUserId // that's the parameter I would like pass in
}
}).then(attendee => {
return (attendee.length > 0 ? true : false);
)};
}
}
// end of what I'm trying to do //
};
}
});
const Query = new GraphQLObjectType({
name: 'Query',
description: 'Root query object',
fields: () => {
return {
events: {
type: new GraphQLList(Event),
args: {
id: {
type: GraphQLInt
}
},
resolve (root, args) {
// here is the parameter I would like to pass to the event object
let currentUserId = root.userId;
////////
return Db.models.event.findAll({ where: args });
}
},
...
Update
The reason I can't just do data.currentUserId = root.userId, is because it's not visible when I'm returned a collection of event objects, since what is passed into my Event GraphQLOBjectType is only the {event} object.
What it looks like when I do data.currentUserId and there is an array of objects inside data is this:
[{objects}, currentUserId: 1]
As opposed to what we want which is this:
[{object, currentUserId: 1}, {anotherObject, currentUserId: 1}]
If I wanted to have access to the currentUserId in the Event GraphQLObject, the only thing I can think of is to loop through every object and add the currentUserId onto it like this:
return events.map(event => {
event.currentUserId = currentUserId;
return event;
});`
Is this the best solution?
I'm afraid you can't do that. fields doesn't recieve any parameters, so you won't send any either.
Fortunately, you can achieve that in more convenient way.
Everything your parent type (Query) returns in resolve function is visible in child resolve's root parameter.
const Query = new GraphQLObjectType({
name: 'Query',
description: 'Root query object',
fields: () => ({
events: {
type: new GraphQLList(Event),
args: {
id: {
type: GraphQLInt
}
},
resolve (root, args) {
return Db.models.event.findAll({ where: args })
.then(data => {
// pass the parameter here
data.currentUserId = root.userId;
return data;
});
}
},
...
Then your Event object would look like this:
const Event = new GraphQLObjectType({
name: 'Event',
description: 'This represents an Event',
fields: () => ({
...
isAttending: {
type: GraphQLBool,
resolve: (event) => {
return event.getAttendees({
where: {
id: event.currentUserId // that's the parameter you've passed through parent resolve
}
}).then(attendee => {
return (attendee.length > 0 ? true : false);
});
}
}
})
});

Resources