neo4j-graphql-js
neo4j-graphql-js copied to clipboard
Apollo Subscriptions Cypher Directive
This is more of a general question, maybe a feature request.
Looking at https://github.com/neo4j-graphql/neo4j-graphql-js/issues/153, it seems the library is compatible with subscriptions.
@johnymontana is the neo4jgraphql function supposed to be compatible with graphql subscriptions? When I tried, it failed with an error on dist/utils.js line 181 "could not access astNode of undefined." The library usesschema.getQueryType
instead of schema.getSubscriptionType
to get the ast node to extract the cypher directive from. It treats subscriptions as regular queries.
https://github.com/albert-the-creator/neo4j-graphql-js/commit/dd59c7bd272981f66224f5ba882896859bd55f51
The commit above allows me to use subscriptions in this manner (extremely raw & crude example):
import { neo4jgraphql } from 'neo4j-graphql-js';
const schema = `
extend type Subscription {
messageSent(userId: ID): Activity
@cypher(
statement: """
MATCH (u:User {id: $userId})-[:SENT_MESSAGE]->(m:Activity {type: 'MESSAGE'})
RETURN m ORDER BY m.createdAt DESC LIMIT 1
"""
)
}
`;
export default {
schema,
resolvers: {
messageSent: {
resolve: (payload, params, context, info) => {
return neo4jgraphql(null, params, context, info);
},
subscribe: () => pubsub.asyncIterator('MESSAGE_SENT')
}
}
};
@albert-the-creator No, we don't support subscriptions yet. In theory, you should be able to implement the subscriptions yourself and use this library for queries and mutations, but I haven't tested that recently.
Adding subscriptions is something we have on the roadmap, but haven't prioritized yet.
First order of business for January? :/
@johnymontana Hi, Will, our team have learnt a lot about Neo4j and GRAND stack from you on the website. Thank you! Subscription is one of the three core (query, mutation, subscription) of GraphQL. We are longing for it so much. Hope you guys prioritize it. 🥇 Thanks a lot. :)
@johnymontana what would be interested is makeAugmentedSchema
to do Subscriptions
automatically. If we assume that each mutation (i.e. Movie
verb Create/Update/Delete
) is publishing to its own type topic and each id of element in the topic is referencing the main type by internal neo4j id then this could happen automatically. In Subscription we can add augmented elements where each on would be subscribing to messages in their own topic (MovieCreate, MovieUpdate, MovieDelete). Received mutation trigger would just allow to display/notify about the mutant.
@johnymontana I've been trying to get subscriptions to work with both apollo-server and graphql-yoga but to no avail. Is there a working example somewhere to show me where I'm off or is this just not possible to implement even with custom resolvers? Thanks!
@erikrahm hey Erik i am building Graphql platform called @gapi aka Graphql API you can find namespace inside here https://github.com/Stradivario/gapi which has implemented working Subscriptions so let me show you some basic examples of it.
First i found out about neo4j-graphql-js before 3 weeks ago and i decided to implement Schema merging with my API so i can work with both Schemas and i can extend already existing API's that i have build with this platform.Basically what i have accomplish is this @rxdi/neo4j extends functionality provided from neo4j-graphql-js i created module out of it so i can inject it like a regular module inside @rxdi ecosystem.Then i am using this module to pass my types or if i have already schema created, it will just extend your API functionality.
You can find example for basic neo4j starter application here:
Typescript Javascript Typescript-With-Subscriptions
Basic Neo4J Graphql Server
Write only Types import them inside types:[] then run your application
import { CoreModule, Module, Bootstrap } from "@gapi/core";
import { VoyagerModule } from "@gapi/voyager";
import { Neo4JModule } from "@rxdi/neo4j";
import { GraphQLObjectType, GraphQLString } from "graphql";
const UserType = new GraphQLObjectType({
name: "User",
fields: () => ({
id: {
type: GraphQLString
},
name: {
type: GraphQLString
}
})
});
@Module({
imports: [
CoreModule.forRoot(),
Neo4JModule.forRoot({
types: [UserType],
password: "your-password",
username: "neo4j",
address: "bolt://localhost:7687",
excludedTypes: {
query: {
exclude: ['']
},
mutation: {
exclude: ['']
}
}
}),
VoyagerModule.forRoot()
]
})
export class AppModule {}
Bootstrap(AppModule).subscribe();
Subscriptions
Basically as far as i know neo4j-graphql-js doesn't provide subscriptions inside the package since it will be used onto different servers (i suppose) let me show you my approach of dealing with subscriptions + Neo4J Graphql.
Here we define our Subscriptions subscribeToUserMessagesBasic
and subscribeToUserMessagesWithFilter
then we have our pubsub initiator CreateMessage
where we send message this.pubsub.publish('CREATE_MESSAGE', response);
graphRequest
method is just Typed wrapper forneo4jgraphql
method for executing programaticaly queries to Graph
Working example can be found here https://github.com/rxdi/starter-neo4j-typescript-complex
import { Controller, Type, Subscription, Subscribe, PubSubService, withFilter, GraphQLInt, GraphQLNonNull, GraphQLString } from "@gapi/core";
import { Message } from "./types/message/message.type";
import { IMessage } from "./core/api-introspection";
import { graphRequest } from "@rxdi/neo4j";
import { GraphQLContext } from "./app.context";
@Controller()
export class AppQueriesController {
constructor(
private pubsub: PubSubService
) {}
@Type(Message)
@Mutation()
async CreateMessage(root, params, ctx: GraphQLContext, resolveInfo): Promise<IMessage> {
const response = await graphRequest<IMessage>(root, params, ctx, resolveInfo);
this.pubsub.publish('CREATE_MESSAGE', response);
return response;
}
@Type(Message)
@Subscribe((self: AppQueriesController) => self.pubsub.asyncIterator('CREATE_MESSAGE'))
@Subscription()
subscribeToUserMessagesBasic(message: IMessage): IMessage {
return message;
}
@Type(Message)
@Subscribe(
withFilter(
(self: AppQueriesController) => self.pubsub.asyncIterator('CREATE_MESSAGE_WITH_FILTER'),
(payload, {id}, context) => {
console.log('Subscribed User: ', id, context);
return true;
}
)
)
@Subscription({
id: {
type: new GraphQLNonNull(GraphQLInt)
}
})
subscribeToUserMessagesWithFilter(message: IMessage): IMessage {
return message;
}
}
To test the example you need to write query
subscription subscribeToUserMessagesBasic {
subscribeToUserMessagesBasic {
messageId
}
}
Open another browser and write CreateMessage query
mutation CreateMessage {
CreateMessage(messageId:"1", channelId:"2") {
messageId
channelId
}
}
You will receive the response inside your subscription. Working example can be found here
@rxdi/neo4j wrapper module can be found here https://github.com/rxdi/neo4j
Starting @gapi project
Just specially for this case i decided to add also starter packs for Neo4J you can just pass argument like so.
gapi new my-project --neo4j-typescript
gapi new my-project --neo4j-javascript
gapi new my-project --neo4j-complex
Documentation for @gapi/cli can be found here This command will clone starter applications that i told about earlier.
More complex approach publishing event inside message queue
When the application starts the whole schema is collected via Decorators applied inside Controllers.Now when we have our schema collected and bootstraping is done next step is to attach all BehaviorSubject Observables to particular resolver and from that step we got Type based string literal enums a.k.a Gapi Effects.They look like that:
import { Effect, OfType, PubSubService } from "@gapi/core";
import { EffectTypes } from "../api-introspection/EffectTypes";
import { GraphQLContext } from "../../app.context";
import { IMessage } from "../api-introspection";
@Effect()
export class MessagesEffect {
constructor(
private pubsub: PubSubService
) {}
@OfType(EffectTypes.CreateMessage)
createMessageEffect(result: IMessage, args, context: GraphQLContext) {
this.pubsub.publish('CREATE_MESSAGE', result);
// this will be triggered when CreateMessage effect is executed
console.log(result, args, context);
}
}
Basically with this approach you don't have to touch your @Controller logic and we can publish some event after the success response for this particular resolver
Clean logic without touching scope of CreateMessage
@Type(Message)
@Mutation({
messageId: {
type: GraphQLString
},
channelId: {
type: GraphQLString
}
})
CreateMessage(root, params, ctx: GraphQLContext, resolveInfo): Promise<IMessage> {
return graphRequest<IMessage>(root, params, ctx, resolveInfo);
}
@Effects needs to be passed inside module like so or they will not work:
import { Module } from "@gapi/core";
import { AppQueriesController } from "./app.controller";
import { MessagesEffect } from "./core/effects/messages.effect";
@Module({
controllers: [AppQueriesController],
effects: [MessagesEffect]
})
export class AppModule {}
You can check for more details how to use @gapi and @rxdi
Main repositories:
Regards to all of you! :))
You can find me in Telegram here:
https://t.me/dependency
https://github.com/neo4j-graphql/neo4j-graphql-js/issues/608