diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index f16f49fa..6559b28e 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -66,6 +66,7 @@ *** xref:ogm/directives.adoc[] *** xref:ogm/selection-set.adoc[] *** xref:ogm/type-generation.adoc[] +*** xref:ogm/subscriptions.adoc[] *** xref:ogm/reference.adoc[] ** xref:driver-configuration.adoc[] diff --git a/modules/ROOT/pages/ogm/subscriptions.adoc b/modules/ROOT/pages/ogm/subscriptions.adoc new file mode 100644 index 00000000..bca80407 --- /dev/null +++ b/modules/ROOT/pages/ogm/subscriptions.adoc @@ -0,0 +1,247 @@ +[[ogm-subscriptions]] +:description: This how-to guide shows how to use the OGM with subscriptions. += How to use the OGM with subscriptions + +The Neo4j GraphQL Library can be used in conjunction with the OGM in order to extend the library's functionalities, or to take advantage of the xref:ogm/private.adoc[`@private` directive]. + +== Usage + +This section shows how to use subscriptions with the OGM inside custom resolvers. + +=== Prerequisites + +. Use the following type definitions: +[source, javascript, indent=0] +---- +const typeDefs = `#graphql + type User { + email: String! + password: String! @private + } +`; +---- + +. Set up a subscriptions plugin instance to be shared between the Library and the OGM constructors: +[source, javascript, indent=0] +---- +const subscriptionsPlugin = new Neo4jGraphQLSubscriptionsSingleInstancePlugin(); +---- + +. Set up a server that supports subscriptions. +See more instructions in the xref:subscriptions/getting-started.adoc#setting-up-server[Getting started page]. + + +=== Adding the OGM + +In order to utilize the `@private` marked field on the `User` type, you need to create the `User` model. +Also, to use the subscriptions, you need to pass in the subscriptions plugin instance to the OGM constructor: + +[source, javascript, indent=0] +---- +const ogm = new OGM({ typeDefs, driver, plugins: { subscriptions: subscriptionsPlugin } }); +const Profile = ogm.model("Profile"); +---- + +Make sure you initialize the OGM instance before using it by adding the following line to the `main()` function: +[source, javascript, indent=0] +---- +await ogm.init(); +---- + +=== Adding a custom resolver + +xref:custom-resolvers/[Custom resolvers] can be used for multiple reasons such as performing data manipulation and checks, or interact with third party systems. +In this case, you only need to create a `User` node with the `password` field set. +You can do that by adding a sign-up mutation. +For example: + +[source, javascript, indent=0] +---- +const resolvers = { + Mutation: { + signUp: async (_source, { username, password }) => { + const [existing] = await User.find({ + where: { + username, + }, + }); + if (existing) { + throw new Error(`User with username ${username} already exists!`); + } + const { users } = await User.create({ + input: [ + { + username, + password, + } + ] + }); + return createJWT({ sub: users[0].id }); + }, + }, +}; +const typeDefs = ` + type Mutation { + signUp(username: String!, password: String!): String! + } +` +---- + +[discrete] +=== Conclusion + +Altogether, it should look like this: + +[source, javascript, indent=0] +---- +const subscriptionsPlugin = new Neo4jGraphQLSubscriptionsSingleInstancePlugin(); +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); +const typeDefs = ` + type User { + email: String! + password: String! @private + } + type Mutation { + signUp(username: String!, password: String!): String! # custom resolver + } +` +const resolvers = { + Mutation: { + signUp: async (_source, { username, password }) => { + const [existing] = await User.find({ + where: { + username, + }, + }); + if (existing) { + throw new Error(`User with username ${username} already exists!`); + } + const { users } = await User.create({ + input: [ + { + username, + password, + } + ] + }); + return createJWT({ sub: users[0].id }); + }, + }, +}; +// Share the subscriptions plugin instance across the Library and the OGM +const neoSchema = new Neo4jGraphQL({ + typeDefs, + driver, + resolvers, + plugins: { + subscriptions: subscriptionsPlugin, + }, +}); +const ogm = new OGM({ typeDefs, driver, plugins: { subscriptions: subscriptionsPlugin } }); +const Profile = ogm.model("Profile"); + +async function main() { + // initialize the OGM instance + await ogm.init(); + + // Apollo server setup with WebSockets + const app = express(); + const httpServer = createServer(app); + const wsServer = new WebSocketServer({ + server: httpServer, + path: "/graphql", + }); + + // Neo4j schema + const schema = await neoSchema.getSchema(); + + const serverCleanup = useServer( + { + schema, + context: (ctx) => { + return ctx; + }, + }, + wsServer + ); + + const server = new ApolloServer({ + schema, + plugins: [ + ApolloServerPluginDrainHttpServer({ + httpServer, + }), + { + async serverWillStart() { + return Promise.resolve({ + async drainServer() { + await serverCleanup.dispose(); + }, + }); + }, + }, + ], + }); + await server.start(); + + app.use( + "/graphql", + cors(), + bodyParser.json(), + expressMiddleware(server, { + context: async ({ req }) => ({ req }), + }) + ); + + const PORT = 4000; + httpServer.listen(PORT, () => { + console.log(`Server is now running on http://localhost:${PORT}/graphql`); + }); +} +---- + + +== Receiving the subscription events + +First, run the following subscription to receive `User` creation events: +[source, gql, indent=0] +---- +subscription { + userCreated { + createdUser { + email + } + event + } +} +---- + +Then run the sign-up mutation: +[source, gql, indent=0] +---- +mutation { + signUp(email: "jon.doe@xyz.com", password: "jondoe") { + email + password + } +} +---- + +The results should look like this: +[source, gql, indent=0] +---- +{ + "data": { + "userCreated": { + "createdUser": { + "email": "jon.doe@xyz.com", + "password": "jondoe" + }, + "event": "CREATE" + } + } +} +---- diff --git a/modules/ROOT/pages/subscriptions/getting-started.adoc b/modules/ROOT/pages/subscriptions/getting-started.adoc index a31578ac..137eaf6e 100644 --- a/modules/ROOT/pages/subscriptions/getting-started.adoc +++ b/modules/ROOT/pages/subscriptions/getting-started.adoc @@ -33,6 +33,7 @@ Then, the next step is to install the following dependencies: npm i --save ws graphql-ws neo4j-driver @neo4j/graphql express @apollo/server body-parser cors ---- +[setting-up-server] == Set up an `@apollo/server` server Add the following code to your `index.js` file to implement a simple `@apollo/server` server with subscriptions (for more options, see link:https://www.apollographql.com/docs/apollo-server/data/subscriptions/[Apollo's documentation]):